Nginx Reverse Proxy Configuration: Routing Traffic by Subdomain to Different Backend Servers


1 views

When implementing a reverse proxy setup with Nginx, one common requirement is routing traffic to different backend servers based on the requested subdomain. This differs from path-based routing (location blocks) and requires server-level configuration.

Here's the fundamental structure for subdomain-based routing in Nginx:

server {
    listen 80;
    server_name app1.example.com;
    
    location / {
        proxy_pass http://backend-server-1;
        include proxy_params;
    }
}

server {
    listen 80;
    server_name app2.example.com;
    
    location / {
        proxy_pass http://backend-server-2;
        include proxy_params;
    }
}

For dynamic subdomain routing, you can use wildcard server names:

server {
    listen 80;
    server_name *.example.com;
    
    # Extract subdomain
    set $subdomain "";
    if ($host ~* ^([a-z0-9-]+)\.example\.com$) {
        set $subdomain $1;
    }
    
    location / {
        proxy_pass http://$subdomain.internal.example.com;
        include proxy_params;
    }
}

For more complex routing scenarios, Nginx's map directive offers better performance than if statements:

map $host $backend {
    hostnames;
    
    default                 http://default-backend;
    
    "app1.example.com"     http://10.0.0.1:8000;
    "app2.example.com"     http://10.0.0.2:8000;
    "~^(?.+)\.example\.com$"  http://$sub.internal.example.com;
}

server {
    listen 80;
    server_name .example.com;
    
    location / {
        proxy_pass $backend;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

When implementing HTTPS, you'll need to handle SSL certificates:

server {
    listen 443 ssl;
    server_name app1.example.com;
    
    ssl_certificate /etc/letsencrypt/live/app1.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/app1.example.com/privkey.pem;
    
    location / {
        proxy_pass http://backend-server-1;
        proxy_ssl_server_name on;
    }
}
  • Enable HTTP/2 for better performance
  • Implement proper caching headers
  • Use keepalive connections to backend servers
  • Consider implementing a load balancer for high-traffic subdomains

Some issues to watch out for:

# Check Nginx configuration
sudo nginx -t

# Verify server name matching
nginx -T | grep server_name

# Debug proxy issues
add_header X-Backend $backend always;

Here's a complete configuration for a SaaS application with multiple customer subdomains:

upstream customer1 {
    server 10.0.1.10:3000;
}

upstream customer2 {
    server 10.0.1.20:3000;
}

map $http_host $customer_backend {
    hostnames;
    
    "customer1.app.example.com" customer1;
    "customer2.app.example.com" customer2;
    default customer1;
}

server {
    listen 80;
    server_name ~^(?.+)\.app\.example\.com$;
    
    location / {
        proxy_pass http://$customer_backend;
        proxy_set_header X-Forwarded-Host $host;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

When you need to route traffic to different backend servers based on subdomains (e.g., api.example.com, app.example.com), Nginx's server blocks provide the perfect solution. Unlike location blocks that handle path-based routing, server blocks can distinguish requests by their Host header.

Here's the fundamental structure for subdomain-based reverse proxying:

# Main configuration file (usually in /etc/nginx/nginx.conf)
http {
    include /etc/nginx/conf.d/*.conf;
    include /etc/nginx/sites-enabled/*;
}

Let's create separate server blocks for api.example.com and app.example.com:

# /etc/nginx/conf.d/api.example.com.conf
server {
    listen 80;
    server_name api.example.com;
    
    location / {
        proxy_pass http://backend-api-server:3000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

# /etc/nginx/conf.d/app.example.com.conf
server {
    listen 80;
    server_name app.example.com;
    
    location / {
        proxy_pass http://frontend-app-server:8080;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

For production environments, you'll want HTTPS support with Let's Encrypt:

# API subdomain with SSL
server {
    listen 443 ssl;
    server_name api.example.com;
    
    ssl_certificate /etc/letsencrypt/live/api.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/api.example.com/privkey.pem;
    
    location / {
        proxy_pass https://secure-backend-api:3443;
        proxy_ssl_verify off;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

# Redirect HTTP to HTTPS
server {
    listen 80;
    server_name api.example.com;
    return 301 https://$host$request_uri;
}

For dynamic subdomain routing (e.g., *.example.com), use a regex pattern:

server {
    listen 80;
    server_name ~^(?.+)\.example\.com$;
    
    location / {
        proxy_pass http://$subdomain.internal:8080;
        proxy_set_header Host $host;
    }
}

When implementing subdomain routing:

  • Use keepalive connections to backend servers
  • Implement proper caching headers
  • Consider using Nginx's resolver for dynamic DNS
  • Monitor connection pools to backend services

Common issues and solutions:

# Check Nginx configuration syntax
nginx -t

# Verify server_name matching
nginx -T | grep server_name

# Check access and error logs
tail -f /var/log/nginx/access.log
tail -f /var/log/nginx/error.log