Fix Nginx Showing the Default Page Instead of Django

Fix the Welcome to nginx page appearing instead of your Django site: enable the site, remove the default server, match server_name, add the HTTPS and IPv6 listeners, and reload after nginx -t.

Problem statement

You configured Nginx for your Django app, but visiting the domain shows "Welcome to nginx!" (or another site hosted on the same server). Sometimes it happens only over HTTPS, only on www, or only for some visitors.

Nginx picks a server block for each request by the address and port it arrived on and the Host header. If no block for that address and port has a matching server_name, the request goes to the default server for that listener, which on a fresh install is the "Welcome" page. So the Django block either is not loaded, does not listen where the request arrives, or does not match the hostname. This guide checks each.

How Nginx picks a server blockRequestIP, port, HostListen match80, 443, IPv4, IPv6server_nameexact, wildcardFallbackdefault_serverWith no matching server_name on that listener, the default server answers instead of Django.
  1. Run sudo nginx -T and confirm your Django server block appears at all.
  2. On Debian and Ubuntu, symlink the site into sites-enabled/ and remove sites-enabled/default.
  3. Make sure server_name lists every hostname, including www.
  4. Make sure the block listens on every port and address family the request uses (443 ssl, [::]:80, [::]:443).
  5. Run sudo nginx -t and then sudo systemctl reload nginx; a failed test means the old config is still active.
  6. Test with curl -H "Host: example.com" http://SERVER_IP/ to rule out DNS and caches.

Step-by-step solution

1. Check that Nginx loaded your configuration

sudo nginx -T 2>/dev/null | grep -nE "^# configuration file|server_name|listen"
why the welcome page winsFindingCauseYour file not in nginx -Tsite not linked into sites-enableddefault has default_serverit catches unmatched hostsserver_name lacks wwwthat host falls to defaultOnly on IPv6 clientsno listen [::]:80 in your blocknginx -T prints the configuration Nginx actually loadeddjango-deployment.com
Each finding points to one of the fixes below.

nginx -T prints the full configuration Nginx would use, file by file. If your site file is not listed, Nginx is not reading it.

On Debian and Ubuntu, nginx.conf includes /etc/nginx/sites-enabled/* (and conf.d/*.conf). Files in sites-available/ do nothing until linked:

sudo ln -s /etc/nginx/sites-available/myapp /etc/nginx/sites-enabled/myapp
ls -l /etc/nginx/sites-enabled/

On RHEL-family systems and the official Nginx packages, configuration usually goes in /etc/nginx/conf.d/ and must end in .conf to match the include pattern.

2. Remove or demote the default site

sudo rm /etc/nginx/sites-enabled/default
a deliberate catch-allserver {listen 80default_server;listen [::]:80 default_server;server_name _;return444;}1231Receives every host no other block matches.2A name that never matches a real host.3Close without a response: scanners get nothing.Your Django block then only answers for its own namesdjango-deployment.com
Add a 443 version with a self-signed certificate if HTTPS scanners bother you.

The packaged default site has listen 80 default_server;, so it wins every request whose host does not match another block. Removing the symlink leaves the file in sites-available if you need it again.

If you want an explicit catch-all for unknown hosts (recommended, so random hostnames and IP-only requests do not reach Django), define it deliberately:

server {
    listen 80 default_server;
    listen [::]:80 default_server;
    server_name _;
    return 444;
}

444 closes the connection without a response. For HTTPS, a catch-all needs a certificate or ssl_reject_handshake on; (Nginx 1.19.4 and newer).

3. Match every hostname

server {
    listen 80;
    listen [::]:80;
    server_name example.com www.example.com;
    # ...
}

server_name example.com; does not match www.example.com. Use .example.com to match the domain and all subdomains, or list each explicitly. Check for typos and for hostnames only defined in another block that returns the wrong content.

4. Listen on the right ports and address families

If the site works over HTTP but HTTPS shows the default page (or another site's certificate), there is no listen 443 ssl block for your hostname. Certbot's --nginx plugin adds one to the matching block; if you ran certbot certonly, you must add it yourself:

server {
    listen 443 ssl;
    listen [::]:443 ssl;
    server_name example.com www.example.com;
    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
    # ... proxy to Gunicorn
}

If only some visitors see the default page, check IPv6: when the domain has an AAAA record, IPv6 visitors connect to [::]:80 or [::]:443. A block that only has listen 80; does not receive them, so the default server for the IPv6 listener answers.

5. Test and reload properly

sudo nginx -t && sudo systemctl reload nginx

If nginx -t fails, the reload never happens and Nginx keeps serving the previous configuration, often the default site. Read the error, fix it, and test again. systemctl status nginx and journalctl -u nginx show errors from failed reloads.

6. Rule out DNS and caches

Send a request straight to the server with the right Host header:

curl -s -H "Host: example.com" http://203.0.113.10/ | head -5
curl -sk --resolve example.com:443:203.0.113.10 https://example.com/ | head -5
is it the server or the path to it?1dig +short example.comA and AAAA point here?2curl -H "Host: example.com" http://IP/bypasses DNS3Django answers?then Nginx is fine4Check CDN or proxy cachepurge old responses5Private windowrules out browser cacheA stale AAAA record sends IPv6 users to an old serverdjango-deployment.com
Test from outside your own network to avoid local DNS overrides.

If these return your Django site but the browser does not, the problem is outside Nginx: DNS points elsewhere (dig +short example.com), a CDN serves a cached default page, or the browser cached a redirect. If they return the default page, the problem is in the Nginx configuration.

Verification check:

sudo nginx -T 2>/dev/null | grep -E "server_name|listen" | sort | uniq -c
curl -sI http://example.com/ | head -3
curl -sI https://example.com/ | head -3
curl -sI https://www.example.com/ | head -3

Every hostname and scheme should reach Django (a Django response, a redirect you configured, or your expected page), never the Nginx welcome page.

Explanation

Nginx first narrows candidate server blocks to those whose listen directives match the IP address and port the connection arrived on. Among those, it compares the Host header to server_name values: exact names, then wildcards, then regular expressions. If nothing matches, it uses the block marked default_server for that listener, or the first one defined. The welcome page is simply the default server winning because your block did not qualify at one of these steps.

Edge cases / notes

Multiple sites on one server

When several Django projects share a server, a missing server_name on one project sends its traffic to another project's block. Give every block explicit names and a deliberate catch-all.

Django's ALLOWED_HOSTS

Once the right block is selected, the request still has to pass ALLOWED_HOSTS. A 400 Bad Request from Django after fixing Nginx means the hostname is missing there.

When to automate this

Generate Nginx site files from a template with the hostnames as variables, remove the default site during provisioning, and run nginx -t in the deploy before reloading. A post-deploy curl for each hostname and scheme catches mismatches immediately.

Next steps

FAQ

Why does my domain show "Welcome to nginx!"?

Nginx did not match the request to your site's server block, so the default server answered. Enable the site, remove the default, and check server_name and listen.

Why does HTTP work but HTTPS shows the wrong site?

There is no listen 443 ssl block for your hostname. Add one with the certificate, or run Certbot's Nginx plugin.

Why do only some visitors see the default page?

Often IPv6: visitors using the AAAA record reach a listener your block does not declare. Add listen [::]:80 and listen [::]:443 ssl.

Do I need to restart Nginx after editing the configuration?

Reload after a successful nginx -t. If the test fails, the reload does not apply and the old configuration keeps running.

Keep going