Optimal Nginx Configuration for Serving Multiple PHP Projects from Subdirectories


2 views

When setting up multiple PHP projects under a single domain with subdirectory paths, we need an Nginx configuration that:

  • Maintains clean URL structures
  • Properly routes PHP requests to FastCGI
  • Preserves path information for relative assets
  • Minimizes configuration duplication

Here's the optimized configuration that solves all the requirements:

server {
    listen 80;
    server_name localhost;
    
    root /var/www/public;
    index index.php index.html;

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

    # Wiki application
    location /wiki {
        alias /var/www/wiki/public;
        try_files $uri $uri/ /wiki/index.php?$query_string;
        
        location ~ \.php$ {
            include fastcgi_params;
            fastcgi_pass 127.0.0.1:9000;
            fastcgi_param SCRIPT_FILENAME $request_filename;
            fastcgi_param DOCUMENT_ROOT $realpath_root;
        }
    }

    # Blog application
    location /blog {
        alias /var/www/blog/public;
        try_files $uri $uri/ /blog/index.php?$query_string;
        
        location ~ \.php$ {
            include fastcgi_params;
            fastcgi_pass 127.0.0.1:9000;
            fastcgi_param SCRIPT_FILENAME $request_filename;
            fastcgi_param DOCUMENT_ROOT $realpath_root;
        }
    }

    # PHP handler for main app
    location ~ \.php$ {
        include fastcgi_params;
        fastcgi_pass 127.0.0.1:9000;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    }
}

The solution uses several important Nginx features:

# Critical directives explained:
alias - Maps URL path to filesystem path while preserving the URI
try_files - Attempts multiple fallback options
$request_filename - Contains full filesystem path
$realpath_root - Resolves symlinks to absolute path

Developers often encounter these issues:

  • 403 Forbidden errors: Ensure proper filesystem permissions (chmod 755 for directories, 644 for files)
  • PHP file downloads: Verify FastCGI process is running and SCRIPT_FILENAME is correct
  • CSS/JS 404s: Use absolute paths in HTML or proper base href tags

For production environments, consider adding:

# Cache static assets
location ~* \.(jpg|jpeg|png|gif|ico|css|js)$ {
    expires 30d;
    add_header Cache-Control "public";
}

# Security headers
add_header X-Frame-Options "SAMEORIGIN";
add_header X-Content-Type-Options "nosniff";
add_header X-XSS-Protection "1; mode=block";

When setting up multiple PHP projects under subdirectories of a single domain in Nginx, developers often encounter path resolution issues with FastCGI. The primary pain points involve:

  • Maintaining clean URI structures (/project/) while serving from different document roots
  • Proper SCRIPT_FILENAME resolution for PHP-FPM
  • Avoiding configuration duplication

Here's a robust solution that handles both static assets and PHP processing:

server {
    listen 80;
    server_name localhost;

    root /var/www/public;
    index index.php index.html;

    # Main location for root requests
    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    # Wiki subdirectory
    location /wiki {
        alias /var/www/wiki/public;
        try_files $uri $uri/ /wiki/index.php?$query_string;
        
        location ~ \.php$ {
            include fastcgi_params;
            fastcgi_pass 127.0.0.1:9000;
            fastcgi_param SCRIPT_FILENAME $request_filename;
            fastcgi_param PATH_INFO $fastcgi_script_name;
        }
    }

    # Blog subdirectory  
    location /blog {
        alias /var/www/blog/public;
        try_files $uri $uri/ /blog/index.php?$query_string;
        
        location ~ \.php$ {
            include fastcgi_params;
            fastcgi_pass 127.0.0.1:9000;
            fastcgi_param SCRIPT_FILENAME $request_filename;
        }
    }

    # Global PHP handler
    location ~ \.php$ {
        include fastcgi_params;
        fastcgi_pass 127.0.0.1:9000;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    }
}

Alias vs Root: The alias directive is crucial for subdirectory mappings as it completely replaces the matched path segment, unlike root which appends to it.

SCRIPT_FILENAME Resolution: Notice the different approaches:

  • For main location: $document_root$fastcgi_script_name
  • For subdirectories: $request_filename (works better with alias)

Shared Configuration: If you have many similar subprojects, consider using regex location matching:

location ~ ^/(?wiki|blog|forum)/ {
    alias /var/www/$project/public;
    try_files $uri $uri/ /$project/index.php?$query_string;
    
    location ~ \.php$ {
        include fastcgi_params;
        fastcgi_pass 127.0.0.1:9000;
        fastcgi_param SCRIPT_FILENAME $request_filename;
    }
}

Security Tip: Always include this in your PHP locations to prevent arbitrary script execution:

fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_param PATH_INFO $fastcgi_path_info;

If you encounter 404 errors:

  1. Verify filesystem permissions
  2. Check that alias paths end with trailing slashes when needed
  3. Confirm PHP-FPM is listening on correct socket/port
  4. Inspect Nginx error logs (/var/log/nginx/error.log)