Skip to main content

Serving Static Files in Nginx: try_files and expires

Nginx · 29.09.2026

Why static files are better served directly

Images, CSS, JS, and fonts do not require running PHP code, so passing them through PHP-FPM is a waste of resources. Nginx can serve files straight from disk, many times faster and with less load on the server. The administrator's job is to tell Nginx exactly which paths to serve directly and which to pass to the backend.

How try_files works

The try_files directive checks files and directories in order and serves the first one found, and if nothing is found it passes the request further:

location / {
    root /var/www/example.com/public;
    try_files $uri $uri/ /index.php?$query_string;
}

Here Nginx first looks for a file with the exact request name, then a directory with that name, and only if nothing was found does it pass the request to index.php. This is the standard scheme for WordPress, Laravel, and most modern PHP applications.

How to serve static files in a separate location

For static files it makes sense to set up a separate block with its own caching settings:

location ~* \.(jpg|jpeg|png|gif|webp|svg|css|js|woff2?)$ {
    root /var/www/example.com/public;
    try_files $uri =404;
    expires 30d;
    add_header Cache-Control "public, immutable";
    access_log off;
}

The access_log off setting for static files reduces disk load: there is no point logging thousands of requests for images and scripts.

Caching and performance directives

DirectiveValueEffect
expires30d, 1yThe Cache-Control and Expires header for the browser
sendfileonServing a file via the sendfile system call, without copying it into Nginx memory
tcp_nopushonSending headers and the start of a file in a single TCP packet
open_file_cachemax=1000 inactive=20sA cache of file descriptors and metadata, to avoid reading the disk again

The sendfile and tcp_nopush directives are enabled once in the http {} section — they work globally and need no per-site configuration.

How to configure open_file_cache

On sites with a large number of small files — icons, avatars, thumbnails — it is useful to enable a descriptor cache:

open_file_cache max=2000 inactive=30s;
open_file_cache_valid 60s;
open_file_cache_min_uses 2;
open_file_cache_errors on;

This reduces the number of open() and stat() system calls for files that are requested often: Nginx keeps their descriptors in memory instead of reading the disk again.

Common mistakes when serving static files

What most often breaks static file delivery:

  • too long an expires value for files without a version in the name — an updated CSS file will not reach the user
  • a regular expression in location without escaping the dot — it will match extra paths
  • missing try_files $uri =404; — a request for a non-existent file goes to PHP-FPM for nothing
  • root specified in two different places in the configuration with different paths — a typical cause of 404 on static files

Summary

A checklist for fast static file delivery:

  • move static extensions into a separate location with try_files $uri =404;
  • set expires to 30 days or more and Cache-Control: immutable for files with a version in the name
  • enable sendfile, tcp_nopush, and open_file_cache in http {}
  • disable access_log for static files to avoid loading the disk

After configuring static files, check response compression with gzip and Brotli and enable HTTP/2 and HTTP/3 support — together with try_files and expires this gives the main boost to site speed.

← Back to Knowledge Base Ask Support