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.