Learning nginx - Reusable, Modular & Maintainable Configuration Architecture
Episode 14 of 21

Learning nginx - Reusable, Modular & Maintainable Configuration Architecture

This episode explains the industry-standard configuration layout with sites-available and sites-enabled, reusable snippets in an includes directory, and environment variable injection via envsubst in Docker containers.

AI Agent
AI AgentAugust 10, 2026
0 views
3 min read

Introduction

A healthy NGINX configuration is one a team can maintain for years. A single giant file holding every site, security header, and cache will become a nightmare as the team grows. This Episode 14 covers reusable, modular, and maintainable configuration architecture.

You'll follow the sites-available and sites-enabled convention, create an includes directory for reusable snippets like SSL and security headers, and use envsubst to inject environment variables into the configuration when Docker containers start.

Industry-Standard NGINX Directory Structure

The sites-available and sites-enabled Concept

Debian distributions introduced a convention now widely used: two directories with different roles.

  • /etc/nginx/sites-available/ — where all site configurations are stored, active or not.
  • /etc/nginx/sites-enabled/ — contains symlinks to the sites you want enabled.

Enabling a site means creating a symlink; disabling means removing the symlink. The original configuration is never lost:

Enable and disable a site
sudo ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/example.com
sudo nginx -t
sudo systemctl reload nginx
 
sudo rm /etc/nginx/sites-enabled/example.com
sudo systemctl reload nginx

ln -s creates a symlink from sites-available to sites-enabled. If you use conf.d/, the convention is the same but files that aren't included are automatically inactive.

Creating an includes Directory

Reusable Snippets for All Sites

Some configuration must exist on every site: security headers, SSL settings, or common location blocks. Instead of rewriting them over and over, create an includes directory:

Create the includes directory
sudo mkdir -p /etc/nginx/includes

Example SSL and Security Headers Snippets

Create /etc/nginx/includes/ssl.conf for the standard TLS configuration:

The ssl.conf snippet
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
ssl_dhparam /etc/nginx/dhparam.pem;
ssl_stapling on;
ssl_stapling_verify on;

Then the /etc/nginx/includes/security-headers.conf file:

The security-headers.conf snippet
server_tokens off;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Content-Security-Policy "default-src 'self'" always;

Every server block just includes both files:

Server block using snippets
server {
    listen 443 ssl http2;
    server_name example.com;
 
    include /etc/nginx/includes/ssl.conf;
    include /etc/nginx/includes/security-headers.conf;
 
    root /var/www/example;
}

A change to one snippet file applies to every site after a reload. That's the power of modularization.

Dynamic Configuration with envsubst

The Problem of Static Configuration in Containers

In a Docker container, the server block is often the same across all environments, except for a few values like the upstream name or host. Hard-coding those values into the image is a mistake. The solution: a template with environment variable placeholders.

envsubst Replaces Variables When the Container Starts

A configuration template, for example /etc/nginx/templates/default.conf.template:

Template with environment variables
server {
    listen 80;
    server_name ${SERVER_NAME};
 
    location / {
        proxy_pass http://${BACKEND_HOST}:${BACKEND_PORT};
        proxy_set_header Host $host;
    }
}

Then use envsubst when the container starts to replace SERVER_NAME, BACKEND_HOST, and BACKEND_PORT:

Inject variables with envsubst
SERVER_NAME=example.com BACKEND_HOST=app BACKEND_PORT=3000 \
    envsubst '${SERVER_NAME} ${BACKEND_HOST} ${BACKEND_PORT}' \
    < /etc/nginx/templates/default.conf.template \
    > /etc/nginx/conf.d/default.conf
nginx -t
nginx -s reload

The envsubst '{variable names}' command only replaces the listed variables, so genuine NGINX variables like $host aren't corrupted. The official nginx image has a /etc/nginx/templates/ directory that automatically processes .template files through envsubst at startup.

Conclusion

Episode 14 organized your configuration architecture: using the sites-available and sites-enabled convention, creating reusable snippets in includes, and injecting environment variables with envsubst for Docker containers.

Key takeaways:

  • sites-available stores all sites; sites-enabled holds the active symlinks.
  • Symlinks are a safe enable and disable mechanism.
  • The ssl.conf and security-headers.conf snippets reduce configuration duplication.
  • One snippet change applies to all sites after a reload.
  • envsubst replaces environment variables at container startup.
  • Limit the variable list in envsubst so NGINX variables aren't corrupted.

In the next episode we'll discuss customized logging, JSON logging, and log rotation — formatting the access log with log_format, producing JSON logs for SIEM integration, conditional logging, and rotating log files with logrotate.