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.
- Run
sudo nginx -Tand confirm your Djangoserverblock appears at all. - On Debian and Ubuntu, symlink the site into
sites-enabled/and removesites-enabled/default. - Make sure
server_namelists every hostname, includingwww. - Make sure the block listens on every port and address family the request uses (
443 ssl,[::]:80,[::]:443). - Run
sudo nginx -tand thensudo systemctl reload nginx; a failed test means the old config is still active. - 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"
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
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
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
- Review a complete configuration in How to Configure Nginx for Django in Production.
- Confirm the domain resolves correctly with Point a Domain at Your Django Server (DNS Setup).
- Separate several projects cleanly with Host Multiple Django Sites on One Server.
- If the right block now returns 502, see Fix Nginx Not Connecting to Gunicorn (Connection Refused).
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.