How to Configure Nginx SSL Subdomain Routing
When you scale microservices, APIs, or internal tools, exposing everything under a single domain path quickly becomes messy. Managing distinct hostnames like api.yourdomain.com, app.yourdomain.com, and ai.yourdomain.com isolated behind HTTPS is the industry standard for production environments.
In this guide, I will walk you through setting up Nginx as a reverse proxy with automated SSL certificate management to route incoming traffic across multiple subdomains safely and efficiently.
What You’ll Need
- Hetzner VPS or Contabo VPS for hosting
- Namecheap if domain needed
- DigitalOcean as alternative hosting
- n8n Cloud or self-hosted instance for workflow automation backends
Table of Contents
- Domain Setup and DNS Wildcard Records
- Provisioning SSL Certificates with Certbot Let’s Encrypt
- Architecting Nginx Server Blocks for Multi-Subdomain Routing
- Hardening SSL and Optimizing Proxy Headers
- Getting Started
Domain Setup and DNS Wildcard Records
Before configuring web server settings, your domain name servers must correctly resolve incoming requests for both individual subdomains and unexpected hostnames to your server address.
I usually register domains on Namecheap or manage records through standard DNS providers. To handle subdomain routing seamlessly, you should create two primary address records pointing directly to your VPS IP address.
Assuming your primary server public IP address is 192.0.2.1, configure the following host records in your DNS dashboard:
| Type | Host | Value | TTL |
|---|---|---|---|
| A Record | @ | 192.0.2.1 | Automatic / 300s |
| A Record | * | 192.0.2.1 | Automatic / 300s |
The wildcard entry (*) ensures that any request sent to anything.yourdomain.com routes directly to your proxy server without requiring you to manually edit DNS records every time you launch a new service.
To verify DNS propagation across public resolvers before requesting SSL certificates, run the following command in your local terminal:
dig +short api.yourdomain.com @8.8.8.8
If the terminal returns 192.0.2.1, your DNS configuration is live, and you are ready to provision certificates.
💡 Fast-Track Your Project: Don’t want to configure this yourself? I build custom n8n pipelines and bots. Message me with code SYS3-HUGO.
Provisioning SSL Certificates with Certbot Let’s Encrypt
Security is non-negotiable in production setups. We will use Certbot to acquire free TLS/SSL certificates from Let’s Encrypt. You can request explicit certificates per subdomain, or issue a wildcard certificate (*.yourdomain.com) using a DNS-01 challenge.
First, log into your server provisioned on Hetzner VPS or DigitalOcean and install Nginx alongside Certbot:
sudo apt-get update
sudo apt-get install -y nginx certbot python3-certbot-nginx
If you prefer issuing dedicated TLS certificates for explicit subdomains via HTTP validation, ensure port 80 is accessible, then execute:
sudo certbot certonly --nginx -d api.yourdomain.com -d app.yourdomain.com -d ai.yourdomain.com --non-interactive --agree-tos -m admin@yourdomain.com
If you prefer a full wildcard certificate covering every potential subdomain under yourdomain.com, use the manual DNS challenge method:
sudo certbot certonly --manual --preferred-challenges dns -d "yourdomain.com" -d "*.yourdomain.com"
Certbot will output a TXT record name and value string. Add this TXT record in your DNS portal, wait 60 seconds for synchronization, and press Enter.
Once successful, Certbot stores full certificate chains and private keys in the following paths on disk:
- Certificate path:
/etc/letsencrypt/live/yourdomain.com/fullchain.pem - Key path:
/etc/letsencrypt/live/yourdomain.com/privkey.pem
Verify that automated certificate renewal is active and working via systemd timer:
sudo systemctl status certbot.timer
sudo certbot renew --dry-run
Architecting Nginx Server Blocks for Multi-Subdomain Routing
With DNS pointing to your server and SSL certificates stored on disk, we can now configure Nginx to receive incoming HTTPS connections and route them to distinct local microservices.
We will set up three distinct subdomains:
api.yourdomain.comrouting to a high-throughput backend service on port 8000. When handling high database traffic behind this endpoint, ensure you follow best practices like Configuring PgBouncer Connection Pooling for PostgreSQL to prevent connection exhaustion.app.yourdomain.comrouting to a web application on port 3000 running background tasks. This app offloads heavy computational jobs to background workers as detailed in Building Distributed Python Task Schedulers with Dramatiq.ai.yourdomain.comrouting to an AI inference microservice on port 5000 that processes JSON payload contracts described in Building Reliable Structured Output Pipelines with OpenAI.
First, clean up the default Nginx installation file to avoid server conflicts:
sudo rm -f /etc/nginx/sites-enabled/default
Next, create the main configuration file for api.yourdomain.com inside /etc/nginx/sites-available/api.yourdomain.com:
server {
listen 80;
listen [::]:80;
server_name api.yourdomain.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name api.yourdomain.com;
ssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
access_log /var/log/nginx/api.access.log;
error_log /var/log/nginx/api.error.log;
location / {
proxy_pass http://127.0.0.1:8000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 90s;
proxy_connect_timeout 90s;
proxy_send_timeout 90s;
}
}
Now, create the configuration file for app.yourdomain.com inside /etc/nginx/sites-available/app.yourdomain.com:
server {
listen 80;
listen [::]:80;
server_name app.yourdomain.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name app.yourdomain.com;
ssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
access_log /var/log/nginx/app.access.log;
error_log /var/log/nginx/app.error.log;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_buffering off;
}
}
Next, construct the server block for ai.yourdomain.com in /etc/nginx/sites-available/ai.yourdomain.com:
server {
listen 80;
listen [::]:80;
server_name ai.yourdomain.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name ai.yourdomain.com;
ssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
access_log /var/log/nginx/ai.access.log;
error_log /var/log/nginx/ai.error.log;
client_max_body_size 50M;
location / {
proxy_pass http://127.0.0.1:5000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 300s;
proxy_connect_timeout 300s;
proxy_send_timeout 300s;
}
}
Enable all three configurations by creating symbolic links pointing to the /etc/nginx/sites-enabled/ directory:
sudo ln -s /etc/nginx/sites-available/api.yourdomain.com /etc/nginx/sites-enabled/
sudo ln -s /etc/nginx/sites-available/app.yourdomain.com /etc/nginx/sites-enabled/
sudo ln -s /etc/nginx/sites-available/ai.yourdomain.com /etc/nginx/sites-enabled/
Verify your syntax before reloading Nginx to prevent configuration syntax errors from dropping live traffic:
sudo nginx -t
When Nginx reports syntax validation OK, apply the changes:
sudo systemctl reload nginx
Hardening SSL and Optimizing Proxy Headers
Default web configurations can leave open vulnerabilities or suffer performance bottlenecks under load. We will add security headers, generate strong Diffie-Hellman parameters, enforce HTTP Strict Transport Security (HSTS), and implement basic rate limiting across subdomains.
Generate a unique 2048-bit Diffie-Hellman group parameter file:
sudo openssl dhparam -out /etc/nginx/dhparam.pem 2048
Next, open your global configuration file located at /etc/nginx/nginx.conf and replace its content completely with this hardened baseline setup:
user www-data;
worker_processes auto;
pid /run/nginx.pid;
include /etc/nginx/modules-enabled/*.conf;
events {
worker_connections 2048;
multi_accept on;
use epoll;
}
http {
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
server_tokens off;
include /etc/nginx/mime.types;
default_type application/octet-stream;
ssl_dhparam /etc/nginx/dhparam.pem;
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:50m;
ssl_session_tickets off;
ssl_stapling on;
ssl_stapling_verify on;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "no-referrer-when-downgrade" always;
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
gzip on;
gzip_disable "msie6";
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
}
To enable the rate limiting zone defined in the global configuration inside our public API server block, update /etc/nginx/sites-available/api.yourdomain.com by inserting the rate-limiting directive inside the primary location block:
server {
listen 80;
listen [::]:80;
server_name api.yourdomain.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name api.yourdomain.com;
ssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
access_log /var/log/nginx/api.access.log;
error_log /var/log/nginx/api.error.log;
location / {
limit_req zone=api_limit burst=20 nodelay;
proxy_pass http://127.0.0.1:8000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 90s;
proxy_connect_timeout 90s;
proxy_send_timeout 90s;
}
}
Validate the modified settings once again:
sudo nginx -t
If everything checks out cleanly, execute a full daemon reload:
sudo systemctl restart nginx
You can test response headers using cURL from your workstation to confirm that HTTP requests automatically upgrade to HTTPS and return security headers:
curl -I https://api.yourdomain.com
Your system will output the HTTP status along with the hardened TLS security headers, confirming that reverse proxy subdomain routing is fully active and protected.
Getting Started
Ready to deploy your infrastructure? Select a server provider and get your domain mapped today:
- Host your infrastructure on high-performance cloud nodes using Hetzner VPS or Contabo VPS.
- Register scalable domains and manage DNS records with Namecheap.
- Launch developer-friendly instances using DigitalOcean.
- Integrate automated webhook and orchestration pipelines using n8n Cloud.
Outsource Your Automation
Don’t have time? I build production n8n workflows, WhatsApp bots, and fully automated YouTube Shorts pipelines. Hire me on Fiverr, mention SYS3-HUGO for priority. Or DM at chasebot.online.
Want to automate this yourself?
Start with n8n Cloud (free tier available) or self-host on a Hetzner VPS for full control.
Want this engine running on your own VPS?
This blog publishes itself — daily, unattended, on free API tiers. The full engine, Hugo theme, and setup guide are available as System 3.
Get System 3