How to Share Nginx Configuration Between HTTP and HTTPS Server Blocks Without Duplication


4 views

Many developers find themselves maintaining nearly identical Nginx server {} blocks for HTTP (port 80) and HTTPS (port 443) configurations. This leads to code duplication and maintenance headaches:

# Bad - Duplicated configuration
server {
  listen 80;
  server_name example.com;
  root /var/www/html;
  index index.html;
  # 20 more directives...
}

server {
  listen 443 ssl;
  server_name example.com;
  root /var/www/html;
  index index.html;
  # Same 20 directives repeated
  ssl_certificate /path/to/cert.pem;
  ssl_certificate_key /path/to/key.pem;
}

The most maintainable approach is to use Nginx's include directive to share common configuration:

# /etc/nginx/common.conf
root /var/www/html;
index index.html;
# All shared directives go here

# /etc/nginx/sites-enabled/example.com
server {
  listen 80;
  server_name example.com;
  include common.conf;
}

server {
  listen 443 ssl;
  server_name example.com;
  include common.conf;
  ssl_certificate /path/to/cert.pem;
  ssl_certificate_key /path/to/key.pem;
}

For simpler cases where you just need to conditionally enable SSL:

map $scheme $ssl_flag {
  https 'on';
  default 'off';
}

server {
  listen 80;
  listen 443 ssl;
  server_name example.com;
  
  ssl $ssl_flag;
  # Shared config here
  
  if ($ssl_flag = 'on') {
    ssl_certificate /path/to/cert.pem;
    ssl_certificate_key /path/to/key.pem;
  }
}

When merging configurations:

  • Test thoroughly - some directives behave differently in HTTP vs HTTPS
  • Watch for mixed content warnings when serving assets
  • Consider using HTTP/2 for HTTPS connections
  • Keep security headers consistent across protocols

Here's how we implemented this on a production site:

# common_config.conf
add_header X-Frame-Options SAMEORIGIN;
add_header X-Content-Type-Options nosniff;
add_header X-XSS-Protection "1; mode=block";

root /var/www/production;
index index.php index.html;
error_log /var/log/nginx/example_error.log;
access_log /var/log/nginx/example_access.log;

location / {
  try_files $uri $uri/ /index.php?$query_string;
}

# HTTPS server
server {
  listen 443 ssl http2;
  server_name example.com;
  include common_config.conf;
  
  ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
  include /etc/letsencrypt/options-ssl-nginx.conf;
}

# HTTP server
server {
  listen 80;
  server_name example.com;
  include common_config.conf;
  return 301 https://$host$request_uri;
}

When setting up both HTTP and HTTPS versions of a website in Nginx, you'll often find yourself writing nearly identical server blocks. The only differences typically being the port (80 vs 443) and SSL-related directives. This leads to configuration duplication that's hard to maintain:

server {
  listen 80;
  server_name example.com;
  root /var/www/html;
  # ... other common directives ...
}

server {
  listen 443 ssl;
  server_name example.com;
  root /var/www/html;
  ssl_certificate /path/to/cert.pem;
  ssl_certificate_key /path/to/key.pem;
  # ... same common directives as above ...
}

The most maintainable approach is to extract the common configuration into a separate file and include it in both server blocks:

# common.conf
root /var/www/html;
index index.html;
# ... other shared directives ...

# nginx.conf
server {
  listen 80;
  server_name example.com;
  include common.conf;
}

server {
  listen 443 ssl;
  server_name example.com;
  ssl_certificate /path/to/cert.pem;
  ssl_certificate_key /path/to/key.pem;
  include common.conf;
}

For simpler cases, you can use a single server block that handles both protocols:

server {
  listen 80;
  listen 443 ssl;
  server_name example.com;
  
  ssl_certificate /path/to/cert.pem;
  ssl_certificate_key /path/to/key.pem;
  
  # Common configuration
  root /var/www/html;
  # ... other directives ...
}

If you need different behavior based on the protocol, use the $scheme variable:

location /secure-area {
  if ($scheme = http) {
    return 301 https://$host$request_uri;
  }
  # HTTPS-only configuration
}
  • Use includes for complex configurations
  • Keep SSL-specific directives in the HTTPS block
  • Consider HTTP/2 for better performance
  • Always redirect HTTP to HTTPS for security
server {
  listen 80;
  server_name example.com;
  return 301 https://$host$request_uri;
}