How to Use Nginx Variables Like $server_name in SSL Certificate Path Configuration


1 views

Many administrators need to configure multiple SSL certificates dynamically in Nginx, especially when managing multiple domains or implementing automated certificate provisioning. The natural approach would be to use variables like $server_name in certificate paths, but this presents technical limitations.

Nginx processes SSL-related directives during configuration parsing rather than request processing. Since $server_name is a request-time variable, it cannot be used in these contexts:

# This WON'T work:
ssl_certificate /path/to/${server_name}.crt;
ssl_certificate_key /path/to/${server_name}.key;

Option 1: Separate Server Blocks

The most straightforward solution is to create individual server blocks for each domain:

server {
    listen 443 ssl;
    server_name example.com;
    ssl_certificate /path/to/example.com.crt;
    ssl_certificate_key /path/to/example.com.key;
    # ... other config
}

server {
    listen 443 ssl;
    server_name example2.com;
    ssl_certificate /path/to/example2.com.crt;
    ssl_certificate_key /path/to/example2.com.key;
    # ... other config
}

Option 2: Wildcard Certificates

For subdomains, consider using wildcard certificates:

server {
    listen 443 ssl;
    server_name *.example.com;
    ssl_certificate /path/to/wildcard.example.com.crt;
    ssl_certificate_key /path/to/wildcard.example.com.key;
    # ... other config
}

Option 3: Configuration Generation

For dynamic environments, generate configurations programmatically:

# Bash script example
for domain in example.com example2.com; do
    cat > /etc/nginx/conf.d/${domain}.conf <

Option 4: Using Lua with OpenResty

If using OpenResty, you can implement dynamic SSL with Lua:

server {
    listen 443 ssl;
    
    ssl_certificate_by_lua_block {
        local domain = ngx.var.server_name
        local cert_path = "/path/to/" .. domain .. ".crt"
        local key_path = "/path/to/" .. domain .. ".key"
        
        local ssl = require "ngx.ssl"
        ssl.clear_certs()
        
        local cert, err = ssl.parse_pem_cert(io.open(cert_path):read("*a"))
        if not cert then
            ngx.log(ngx.ERR, "failed to parse cert: ", err)
            return ngx.exit(ngx.ERROR)
        end
        
        local priv_key, err = ssl.parse_pem_priv_key(io.open(key_path):read("*a"))
        if not priv_key then
            ngx.log(ngx.ERR, "failed to parse key: ", err)
            return ngx.exit(ngx.ERROR)
        end
        
        local ok, err = ssl.set_cert(cert)
        if not ok then
            ngx.log(ngx.ERR, "failed to set cert: ", err)
            return ngx.exit(ngx.ERROR)
        end
        
        local ok, err = ssl.set_priv_key(priv_key)
        if not ok then
            ngx.log(ngx.ERR, "failed to set key: ", err)
            return ngx.exit(ngx.ERROR)
        end
    }
}
  • Store certificates in a consistent directory structure
  • Implement proper file permissions (root:root with 600 permissions)
  • Use symbolic links for certificate rotation
  • Consider using Let's Encrypt with certbot for automated management

When configuring multiple SSL certificates in Nginx, you might want to dynamically set certificate paths using variables like $server_name. However, Nginx's configuration parser evaluates these directives at startup, making direct variable interpolation problematic.

The main limitation comes from how Nginx processes its configuration:

# This WON'T work as expected:
ssl_certificate /path/to/${server_name}.crt;
ssl_certificate_key /path/to/${server_name}.key;

Nginx evaluates these paths during configuration parsing, before it knows the actual server name from incoming requests.

Option 1: Using map Directive

Create a mapping between server names and certificate paths:

map $host $cert_path {
    default        /default/path/cert.crt;
    example.com    /certs/example_com.crt;
    test.com       /certs/test_com.crt;
}

map $host $key_path {
    default        /default/path/key.key;
    example.com    /certs/example_com.key;
    test.com       /certs/test_com.key;
}

server {
    listen 443 ssl;
    ssl_certificate $cert_path;
    ssl_certificate_key $key_path;
    ...
}

Option 2: Multiple Server Blocks

For a manageable number of domains, consider separate server blocks:

server {
    server_name example.com;
    ssl_certificate /certs/example_com.crt;
    ssl_certificate_key /certs/example_com.key;
    ...
}

server {
    server_name test.com;
    ssl_certificate /certs/test_com.crt;
    ssl_certificate_key /certs/test_com.key;
    ...
}

Option 3: Lua Scripting (OpenResty)

If using OpenResty, you can leverage Lua for dynamic certificate loading:

server {
    listen 443 ssl;
    
    ssl_certificate_by_lua_block {
        local ssl = require "ngx.ssl"
        local host = ngx.var.host
        local cert = io.open("/certs/"..host..".crt"):read("*a")
        local key = io.open("/certs/"..host..".key"):read("*a")
        
        ssl.clear_certs()
        ssl.set_der_cert(cert)
        ssl.set_der_priv_key(key)
    }
    ...
}
  • Keep certificate files organized in a consistent directory structure
  • Use wildcard certificates when possible for subdomains
  • Consider using ACME clients like certbot for automatic certificate management
  • Implement proper file permissions for certificate directories

While dynamic certificate loading offers flexibility, it comes with performance overhead:

  • File I/O operations on every SSL handshake
  • Potential memory usage with many certificates
  • Added complexity in configuration

For high-traffic sites, the multiple server blocks approach often provides the best balance of performance and maintainability.