Moving an application from Apache to nginx usually starts with someone trying to convert thirty lines of .htaccess rule by rule. Most of it does not need converting: the standard front-controller block is three mod_rewrite directives expressing something nginx has as a single built-in.
# .htaccess — per directory, re-read on every request
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^ index.php [L]
# nginx — one server block, read once at start
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ /. { deny all; } # what "deny from all" was for
location ~ .php$ {
fastcgi_pass unix:/var/run/php5-fpm.sock;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
The two RewriteCond lines are precisely what try_files does natively — try the file, try the directory, fall through to the front controller — which is why the honest translation is three lines against thirty. Converting each RewriteRule into an nginx rewrite ... last instead is how people end up with rewrite loops and a 500 that says nothing. The structural difference matters more than the syntax: nginx has no per-directory configuration and reads its config once at startup, so an application that shipped an .htaccess to set expires headers or deny access to an upload directory now does nothing at all until someone edits the server block and reloads. That is a real loss of convenience on shared hosting and a real gain everywhere else — the routing is in one file, in version control, and nothing is stat-ing directories on every request looking for rules that are not there.