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