Subdomains let one domain host several sites and apps: www.example.com for the main site, blog.example.com for a blog, app.example.com for a web app, status.example.com for monitoring. With Nginx, each subdomain is just another server block, on the same VPS and IP address.

1. Create DNS records

At your DNS provider, add an A record for each subdomain, pointing to your server’s IP:

Type Name Value
A blog 203.0.113.10
A app 203.0.113.10
CNAME status example.com (alternative to an A record)

Check propagation with dig +short blog.example.com. See DNS records explained and DNS propagation.

2. A server block for a static subdomain

sudo mkdir -p /var/www/blog.example.com
# /etc/nginx/sites-available/blog.example.com
server {
    listen 80;
    server_name blog.example.com;
    root /var/www/blog.example.com;
    index index.html;
    location / { try_files $uri $uri/ =404; }
}
sudo ln -s /etc/nginx/sites-available/blog.example.com /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx

Nginx chooses the server block by matching the request’s Host header against server_name. See Nginx server blocks.

3. A subdomain for an app (reverse proxy)

# /etc/nginx/sites-available/app.example.com
server {
    listen 80;
    server_name app.example.com;
    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }
}

That’s the pattern for Node.js, Python, Docker containers, or self-hosted apps (Grafana, Uptime Kuma, Gitea). See Nginx reverse proxy and reverse proxy for multiple Docker containers.

4. HTTPS for each subdomain

sudo certbot --nginx -d blog.example.com
sudo certbot --nginx -d app.example.com

Or one certificate for several names: -d example.com -d www.example.com -d blog.example.com. See Let’s Encrypt with Certbot and multiple domains with SSL.

5. Wildcard subdomains

For many dynamic subdomains (for example customer1.example.com, customer2.example.com in a SaaS):

  • DNS: *.example.com A record → your IP.
  • Nginx: server_name *.example.com;, or a regex that captures the name:
server {
    listen 443 ssl;
    server_name ~^(?<tenant>[a-z0-9-]+)\.example\.com$;
    # ssl_certificate for *.example.com
    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_set_header Host $host;
        proxy_set_header X-Tenant $tenant;
    }
}

Exact names (app.example.com) take priority over wildcards, so you can mix both.

6. A default catch-all server

Requests for unknown hostnames (or your raw IP) otherwise land on the first server block, which might be a site you didn’t intend to show. Add a default that rejects them:

server {
    listen 80 default_server;
    listen 443 ssl default_server;
    server_name _;
    ssl_reject_handshake on;      # Nginx 1.19.4+: no certificate needed
    return 444;
}

Troubleshooting

Problem Check
Subdomain shows another site server_name typo, missing symlink in sites-enabled, or no default server
DNS doesn’t resolve Record missing, or not yet propagated (dig)
Certificate warning Certificate doesn’t include that name; run Certbot for it
502 on app subdomain App not running on the proxied port

Quick reference

Task How
DNS A record sub → server IP
Site New server block with server_name sub.example.com
App proxy_pass http://127.0.0.1:PORT
HTTPS certbot --nginx -d sub.example.com
Wildcard *.example.com DNS + DNS-01 cert
Default default_server returning 444

Related: redirect www to non-www and Nginx test and reload.

  • Nginx
  • DNS
  • Web Hosting