Dynamic Root Directory Configuration in Nginx Based on Server_name for Local Development


7 views

When working on multiple local development projects, creating individual Nginx configurations for each test domain becomes tedious. Each new site typically requires:

1. Creating a new config file in /etc/nginx/sites-available
2. Creating a symbolic link in /etc/nginx/sites-enabled
3. Reloading or restarting Nginx

Nginx provides several built-in variables that can be used for dynamic configuration. The most relevant for our case is $host, which contains the domain name from the request.

Here's the minimal configuration that solves our problem:

server {
    listen 80;
    server_name ~^(?.+)\.loc$;
    root /www/$subdomain;
    index index.html index.htm index.php;
    
    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }
    
    # PHP processing if needed
    location ~ \.php$ {
        include snippets/fastcgi-php.conf;
        fastcgi_pass unix:/run/php/php8.1-fpm.sock;
    }
}

For more complex setups, consider these enhancements:

1. Fallback Directory:

set $site_root /www/$host;
if (!-d $site_root) {
    set $site_root /www/default;
}
root $site_root;

2. HTTPS Support:

server {
    listen 443 ssl;
    server_name ~^(?.+)\.loc$;
    ssl_certificate /etc/ssl/certs/localhost.crt;
    ssl_certificate_key /etc/ssl/private/localhost.key;
    root /www/$subdomain;
    # ... rest of configuration
}

Permission Problems:

sudo chown -R $USER:$USER /www
sudo chmod -R 755 /www

Cache Issues:
Add this to prevent browser caching during development:

add_header Last-Modified $date_gmt;
add_header Cache-Control 'no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0';
if_modified_since off;
expires off;
etag off;

For more complex domain patterns, you can use regex captures:

server {
    listen 80;
    server_name ~^dev-(?[a-z0-9-]+)\.loc$;
    root /www/projects/$project/public;
    # ... rest of configuration
}

This would map dev-myapp.loc to /www/projects/myapp/public.

For true efficiency, combine this with automatic DNS resolution. Add this to your /etc/hosts:

127.0.0.1   mysite.loc
127.0.0.1   myothersite.loc
127.0.0.1   something.loc
127.0.0.1   *.loc

Or use dnsmasq for wildcard DNS resolution.


When working with multiple local development sites, creating individual Nginx configurations for each domain quickly becomes tedious. The traditional approach requires:

# Traditional approach (time-consuming)
/etc/nginx/sites-available/mysite.loc
/etc/nginx/sites-available/myothersite.loc
/etc/nginx/sites-available/something.loc

Nginx provides a simple yet powerful solution through its built-in variables. The $host variable contains the domain name from the HTTP request header, which we can leverage for dynamic root directory selection:

server {
    listen 80;
    server_name ~^(?.+)\.loc$;
    root /www/$subdomain;
    index index.html;
    
    location / {
        try_files $uri $uri/ =404;
    }
}

For more complex setups, consider these enhancements:

# With fallback to default directory
map $host $root_path {
    default /www/default;
    "~^(?.+)\.loc$" /www/$domain;
}

server {
    listen 80;
    server_name ~.loc$;
    root $root_path;
    
    # Enable directory listing for development
    autoindex on;
    
    # PHP-FPM support
    location ~ \.php$ {
        include snippets/fastcgi-php.conf;
        fastcgi_pass unix:/run/php/php7.4-fpm.sock;
    }
}

Here's a complete working example with error handling:

server {
    listen 80;
    server_name ~^(?.+)\.loc$;
    
    root /www/$devsite/public;
    
    access_log /var/log/nginx/$devsite-access.log;
    error_log /var/log/nginx/$devsite-error.log;
    
    index index.php index.html index.htm;
    
    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }
    
    location ~ \.php$ {
        include fastcgi_params;
        fastcgi_pass 127.0.0.1:9000;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    }
    
    location ~ /\.ht {
        deny all;
    }
}

While this approach is convenient for development, consider these performance optimizations:

  • Enable Nginx cache for static assets
  • Disable access logs for development environments
  • Implement proper gzip compression

When using this pattern:

# Restrict directory traversal
location ~ \. {
    deny all;
    return 444;
}

# Prevent access to hidden files
location ~ /\. {
    deny all;
}