Point a Domain at Your Django Server (DNS Setup)
Connect a domain to a Django server: A and AAAA records, www and apex, CNAMEs for platforms, TTLs, verifying propagation with dig, and matching ALLOWED_HOSTS and certificates.
Problem statement
The Django app runs on the server and responds on its IP address, but example.com does not reach it, reaches an old server, works without www but not with it, or shows a certificate error. DNS is the step between "the server works" and "the site works", and it is easy to get subtly wrong because changes take time to propagate and different resolvers can give different answers.
This guide covers the records a Django deployment needs, how to verify them precisely, and the three places in your stack that must agree with DNS: the web server's server_name, Django's ALLOWED_HOSTS, and the TLS certificate.
- For a VPS with a fixed IP, create an
Arecord forexample.compointing at the IPv4 address, and anAAAArecord if the server has IPv6. - Create
www.example.comas aCNAMEtoexample.com(or its ownA/AAAArecords). - For a PaaS, use the
CNAMEtarget the platform gives you; for the apex domain, use the provider'sALIAS/ANAME/CNAME flattening or the platform's A records. - Lower the TTL (for example to 300 seconds) a day before moving a live domain.
- Verify with
dig +short example.com Aagainst public resolvers and the authoritative nameserver. - Add both hostnames to Nginx
server_name, DjangoALLOWED_HOSTSandCSRF_TRUSTED_ORIGINS, and the certificate.
Step-by-step solution
1. Find where DNS is managed
The registrar sells the domain; the DNS provider answers queries for it. They may be the same company or not. Check which nameservers are authoritative:
dig +short NS example.com
Edit records at the provider those nameservers belong to. Changes made at a registrar whose nameservers are not in use have no effect, which is a common source of confusion.
2. Create the records
For a server at 203.0.113.10 with IPv6 2001:db8::10:
| Name | Type | Value | TTL |
|---|---|---|---|
example.com (often @) | A | 203.0.113.10 | 300 |
example.com | AAAA | 2001:db8::10 | 300 |
www | CNAME | example.com. | 300 |
Only publish an AAAA record if the server really accepts connections on that IPv6 address (and Nginx has listen [::]:80; and listen [::]:443 ssl;). A wrong or unreachable AAAA record breaks the site for IPv6 users while it works for you on IPv4.
A CNAME cannot coexist with other records of the same name, which is why the apex (example.com) cannot be a plain CNAME. Platforms that give a hostname target (Render, Heroku, Fly.io, App Platform, and others) document either A records for the apex or a provider feature such as ALIAS, ANAME, or CNAME flattening.
3. Understand TTLs before changing live records
TTL is how long resolvers may cache an answer. If a record has a TTL of 86400 (one day), some visitors can keep reaching the old server for a day after you change it. Before a migration:
- Lower the TTL to 300 at least one old-TTL period ahead.
- Change the record.
- Keep the old server running (or redirecting) until traffic stops.
- Raise the TTL again once stable.
4. Verify precisely
Query the authoritative nameserver first; it shows what you configured, with no caching:
NS=$(dig +short NS example.com | head -1)
dig +short @"$NS" example.com A
dig +short @"$NS" example.com AAAA
dig +short @"$NS" www.example.com
Then check public resolvers, which show what visitors get:
dig +short @1.1.1.1 example.com A
dig +short @8.8.8.8 example.com A
Test the server directly before DNS is updated, by forcing the resolution in curl:
curl -sI --resolve example.com:80:203.0.113.10 http://example.com/ | head -1
--resolve sends the right Host header to the chosen IP, which exercises Nginx server_name and Django ALLOWED_HOSTS exactly as real traffic will.
5. Align Nginx, Django, and TLS
Nginx:
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
# ...
}
Django:
ALLOWED_HOSTS = ["example.com", "www.example.com"]
CSRF_TRUSTED_ORIGINS = ["https://example.com", "https://www.example.com"]
Certificate (once DNS resolves to the server, since the HTTP challenge connects to it):
sudo certbot --nginx -d example.com -d www.example.com
Pick one canonical hostname and redirect the other to it, so cookies, analytics, and search engines see one site:
server {
listen 443 ssl;
server_name www.example.com;
# ssl_certificate lines
return 301 https://example.com$request_uri;
}
6. Email and other records
Adding web records does not affect email as long as you do not delete existing MX, TXT (SPF, DKIM, DMARC), or verification records. When moving DNS to a new provider, copy every record from the old zone first, not only the web ones.
Verification check:
dig +short example.com A
dig +short www.example.com
curl -sI https://example.com/ | head -1
curl -sI https://www.example.com/ | grep -i location
Expect your server's IP, the www alias, a 200 for the canonical host, and a single redirect from the other.
Explanation
When a browser opens https://example.com/, it resolves the name to an IP, connects to port 443, and sends the hostname twice: in the TLS handshake (SNI), to select the certificate, and in the HTTP Host header, to select the Nginx server block and pass Django's ALLOWED_HOSTS check. That is why a hostname has to be present in DNS, in the certificate, in server_name, and in ALLOWED_HOSTS. Missing it from any one of them produces a different error: no resolution, a certificate warning, the default Nginx page, or a 400 Bad Request.
Edge cases / notes
Proxied DNS
With Cloudflare's proxy (orange cloud) or a similar service, DNS returns the proxy's IPs, not yours. dig then shows Cloudflare addresses, which is expected. Certificate issuance and real client IP handling change accordingly.
Stale local caches
Your laptop, router, or ISP may cache old answers. Test with dig @1.1.1.1 or from another network before concluding that DNS is wrong. On Linux with systemd-resolved, resolvectl flush-caches clears the local cache.
Rollback
Keep the old record values written down. Rolling back a DNS change is changing the record back, and it takes effect after the TTL you had set, which is another reason to lower TTLs before migrations.
When to automate this
DNS records can be managed as code with Terraform or the provider's API. That makes environment setup repeatable and gives you history for every change, which matters once several people touch DNS.
Next steps
- Issue certificates once DNS resolves with Configure HTTPS for Django with Let's Encrypt.
- If Django returns
400 Bad Request, see How to Fix DisallowedHost in Django Production. - Set up the server side with Deploy Django with Gunicorn and Nginx on Ubuntu.
- If the domain shows "Welcome to nginx!", follow Fix Nginx Showing the Default Page Instead of Django.
FAQ
How long does DNS propagation take?
Changes are visible at the authoritative nameserver immediately. Resolvers see them when their cached answer expires, which depends on the previous TTL, typically minutes to a day.
Should www be a CNAME or an A record?
Either works. A CNAME to the apex means you only update one IP when the server changes.
Why does my domain work but www does not?
www has no DNS record, is missing from server_name or ALLOWED_HOSTS, or is not on the certificate. Check each with dig, nginx -T, Django settings, and certbot certificates.
Can I point the apex domain at a Heroku or Render hostname?
Not with a plain CNAME. Use your DNS provider's ALIAS, ANAME, or CNAME flattening, or the IP addresses the platform documents for apex domains.