Nginx Config on LEMP stack: Is try_files really necessary?

Garrett Leyenaar asked:

Every single LEMP stack Nginx configuration tutorial I’ve read online says that you need to add this code to the try_files directive

try_files $uri $uri/ /index.php$is_args$args;

But the problem with this code is my custom 404 page will not display anymore. Obviously the problem is because the try_files falls back to index.php – but I don’t like the behavior of example.com/nothing/to/see/here being redirected to the homepage

If I change the try_files directive to…

try_files $uri $uri/ =404;

…it works perfectly as expected. My PHP scripts still run because I have this in my config too:

location ~* \.php$ {
  fastcgi_pass unix:/run/php/php7.2-fpm.sock;
  include         fastcgi_params;
  fastcgi_param   SCRIPT_FILENAME    $document_root$fastcgi_script_name;
  fastcgi_param   SCRIPT_NAME        $fastcgi_script_name;
}

My question is, why do all the tutorials online insist on:

try_files $uri $uri/ /index.php$is_args$args;        ????

I even commented out the try_files and my site still behaves correctly

#try_files $uri $uri/ /index.php$is_args$args;

Is try_files necessary considering it works as expected with it commented out?

I am trying to organize my nginx.conf and site.conf files so I have a master to work from when I setup new servers.

Full nginx.conf

user www-data;
worker_processes auto;
pid /run/nginx.pid;
include /etc/nginx/modules-enabled/*.conf;

events {
    worker_connections 768;
    # multi_accept on;
}

http {
    # Basic Settings
    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;
    keepalive_timeout 65;
    types_hash_max_size 2048;
    server_tokens off;

    # server_names_hash_bucket_size 64;
    # server_name_in_redirect off;

    fastcgi_connect_timeout 300;
    fastcgi_send_timeout 300;
    fastcgi_read_timeout 300;
    fastcgi_buffer_size 64k;
    fastcgi_buffers 4 64k;
    fastcgi_busy_buffers_size 128k;
    fastcgi_temp_file_write_size 128k;
    fastcgi_intercept_errors on;

    include /etc/nginx/mime.types;
    default_type application/octet-stream;

    # SSL Settings
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2; # Dropping SSLv3, ref: POODLE
    ssl_prefer_server_ciphers on;
     # Logging Settings
    access_log /var/log/nginx/access.log;
    error_log /var/log/nginx/error.log;

    #Gzip Compression
    gzip on;
    gzip_buffers 16 8k;
    gzip_comp_level 6;
    gzip_http_version 1.1;
    gzip_min_length 256;
    gzip_proxied any;
    gzip_vary on;
    gzip_types
      text/xml application/xml application/atom+xml application/rss+xml application/xhtml+xml image/svg+xml
      text/javascript application/javascript application/x-javascript
      text/x-json application/json application/x-web-app-manifest+json
      text/css text/plain text/x-component
      font/opentype application/x-font-ttf application/vnd.ms-fontobject
      image/x-icon;
    gzip_disable "MSIE [1-6]\.(?!.*SV1)";

    include /etc/nginx/conf.d/*.conf;
    include /etc/nginx/sites-enabled/*;
}

And my full site.conf

server {
    listen         80;
    server_name    example.com www.example.com;
    return 301     https://example.com$request_uri;
}

server {
    listen         443;
    server_name    www.example.com;
    ssl            on;
    ssl_certificate_key /etc/sslmate/example.com.key;
    ssl_certificate /etc/sslmate/example.com.chained.crt;
    return 301     https://example.com$request_uri;
}

server {
    listen         443;
    server_name    example.com;
    root           /home/garrett/domains/example.com/public_html;
    access_log     /home/garrett/domains/example.com/logs/access.log;
    error_log      /home/garrett/domains/example.com/logs/error.log;
    index          index.php index.html index;
    error_page     404 /404.php;

    ssl on;
    ssl_certificate_key /etc/sslmate/example.com.key;
    ssl_certificate /etc/sslmate/example.com.chained.crt;

    # Recommended security settings from https://wiki.mozilla.org/Securit/Server_Side_TLS
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
    ssl_ciphers 'ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES1$
    ssl_prefer_server_ciphers on;
    ssl_dhparam /usr/share/sslmate/dhparams/dh2048-group14.pem;
    ssl_session_timeout 5m;
    ssl_session_cache shared:SSL:5m;

    # Enable this if you want HSTS (recommended)
    add_header Strict-Transport-Security max-age=15768000;

location / {
     #try_files $uri $uri/ /index.php$is_args$args;
     try_files $uri $uri/ =404;
}
location ~* \.php$ {
     fastcgi_pass unix:/run/php/php7.2-fpm.sock;
     include         fastcgi_params;
     fastcgi_param   SCRIPT_FILENAME    $document_root$fastcgi_script_name;
     fastcgi_param   SCRIPT_NAME        $fastcgi_script_name;
}

location = /favicon.ico {
     log_not_found off;
     access_log off;
}

location = /robots.txt {
     allow all;
     log_not_found off;
     access_log off;
}

location ~* \.(js|css|png|jpg|jpeg|gif|ico)$ {
     expires max;
     log_not_found off;
}

}

My answer:


This particular invocation of try_files (and similar ones) supports the front controller pattern, where a single PHP file is called regardless of the URL, and the URL paths you design can then be arbitrary (and friendly to search engines).

It sounds like you are not using this pattern, but simply creating distinct PHP files for each URL. Only the most trivial of programs should do this. Stop now and redesign your app, while you still can do it easily.


View the full question and any other answers on Server Fault.

Creative Commons License
This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.