When migrating from Apache to Nginx, one of the most common pain points is replicating Apache's mod_rewrite functionality. The specific case we're examining involves routing all requests (except actual files/directories) to a single PHP script (index.php), which was previously handled by this .htaccess rule:
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^.+$ index.php [L]
</IfModule>
The equivalent Nginx configuration requires proper use of try_files
directive combined with location matching. Here's the essential implementation:
server {
listen 80;
server_name swingset.serverboy.net;
root /var/www/swingset;
index index.php index.html;
location / {
try_files $uri $uri/ /index.php?$args;
}
location ~ \.php$ {
include fastcgi_params;
fastcgi_pass 127.0.0.1:9000;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_intercept_errors on;
}
}
try_files directive: This attempts to serve the requested URI as a file ($uri
), then as a directory ($uri/
), and finally falls back to index.php
while preserving query parameters (?$args
).
PHP handling: The second location block ensures PHP files are processed by FastCGI, matching Apache's PHP module behavior.
For more complex routing scenarios, you might need additional configurations:
# Handle clean URLs without query string
location / {
try_files $uri $uri/ @rewrite;
}
location @rewrite {
rewrite ^/(.*)$ /index.php?_url=/$1;
}
# Additional security for PHP files
location ~* \.php$ {
fastcgi_pass unix:/var/run/php/php7.4-fpm.sock;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
# Security headers
add_header X-Frame-Options "SAMEORIGIN";
add_header X-XSS-Protection "1; mode=block";
add_header X-Content-Type-Options "nosniff";
}
1. Missing fastcgi_param: Forgetting to set SCRIPT_FILENAME
will result in blank responses
2. Incorrect root path: Ensure the root directive points to your application's document root
3. Cache issues: During testing, disable browser caching to see immediate changes
For high-traffic sites, consider adding these optimizations:
# Cache file metadata
open_file_cache max=1000 inactive=20s;
open_file_cache_valid 30s;
open_file_cache_min_uses 2;
# Buffer settings
fastcgi_buffers 16 16k;
fastcgi_buffer_size 32k;
# Timeouts
fastcgi_read_timeout 300;
When migrating from Apache to Nginx, one of the most common pain points is replicating Apache's mod_rewrite behavior. The specific case we're addressing here involves routing all requests (except those for existing files/directories) to a single PHP script - typically index.php for front controller patterns.
The original Apache configuration uses .htaccess with these key directives:
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^.+$ index.php [L]
</IfModule>
This does three things:
- Checks if the requested file doesn't exist (!-f)
- Checks if the requested directory doesn't exist (!-d)
- If both conditions are met, routes the request to index.php
Here's the complete Nginx server block that accomplishes the same functionality:
server {
listen 80;
server_name yourdomain.com;
root /var/www/your_project;
index index.php;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
include fastcgi_params;
fastcgi_pass unix:/var/run/php/php7.4-fpm.sock;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
}
location ~ /\.ht {
deny all;
}
}
try_files directive: This is Nginx's equivalent to Apache's rewrite conditions. It checks for files in this order:
- The exact requested URI ($uri)
- The requested URI as a directory ($uri/)
- Finally falls back to index.php while preserving query strings
For more complex routing needs, you might want to:
# Handle pretty URLs without query strings
location / {
try_files $uri $uri/ /index.php$is_args$args;
}
# Alternative for PHP frameworks
location / {
try_files $uri /index.php$request_uri;
}
# With PATH_INFO support
location ~ ^/index\.php(/|$) {
fastcgi_split_path_info ^(.+\.php)(/.+)$;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
fastcgi_pass 127.0.0.1:9000;
}
Unlike Apache's .htaccess files (which are read on every request), Nginx configurations are loaded at startup. This means:
- Changes require server reload (nginx -s reload)
- No per-directory configuration overhead
- Generally better performance for high-traffic sites
Always verify your configuration before applying changes:
nginx -t
Then reload the configuration:
nginx -s reload
Watch out for these issues:
- Incorrect root directory paths
- Missing fastcgi_param SCRIPT_FILENAME
- PHP-FPM not running or misconfigured
- File permission issues
Here's a production-tested configuration for a Laravel application:
server {
listen 80;
server_name laravelapp.com;
root /var/www/laravel/public;
add_header X-Frame-Options "SAMEORIGIN";
add_header X-XSS-Protection "1; mode=block";
add_header X-Content-Type-Options "nosniff";
index index.php;
charset utf-8;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location = /favicon.ico { access_log off; log_not_found off; }
location = /robots.txt { access_log off; log_not_found off; }
error_page 404 /index.php;
location ~ \.php$ {
fastcgi_pass unix:/var/run/php/php8.0-fpm.sock;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
include fastcgi_params;
}
location ~ /\.(?!well-known).* {
deny all;
}
}