Roots Discourse

Nginx returns 404 for images using correct URL

I’m starting to see a very odd problem. I’m only seeing it on client websites we host with Trellis. All the clients that have reported this issue have their servers now on DigitalOcean.

Some asset (images) URLs return 404, even though the file exists and the permissions and ownership on it are correct (664, owned by web:www-data).

An example path for one of these images is /web/app/uploads/cache/2016/03/water-dragon/2806819678.jpg

I thought it be an Nginx config issue and that I might need to edit rewrite rules to account for the file being another folder level down, but this issue also occurs under canonical structures.

I tried copying the file into the higher-level /web/app/uploads/cache/2016/03/ folder, ls shows the file is there, the browser returns 404 when trying to access it. That’s consistent with this issue happening with other clients - they don’t have nested files.

I rsync’d the file to my home folder and can open it just fine.

Weirdly enough, the filename changes colors in the ssh session shell when copied to the higher-level folder when using ls to list. I can’t figure out what the colors represent if anything at all.

This is the stupidest bug to consider switching away from trellis for, but it’s happening frequently enough that my clients are questioning why I switched them from their comfortable shared or managed hosting plans. Does anyone have any insight into why nginx would fail to return a perfectly valid URL path?

Happens with images of all shapes and sizes and formats, and while I have had issues with ACF PRO scripts not being founds (js and css scripts), I haven’t really seen it happen to other css or js files, so I hesitate to assume it’s the same bug, but it might.

I’m using different versions of trellis with these clients, but this example specifically is a release from around a month ago - I don’t think it’s my trellis version being out of date.

Has anyone run into behavior like this and/or have any insight into what’s going on and why?

  1. What Trellis version are you currently using on that server (check the first line in file in the ansible project folder),
  2. What is logged by nginx to site error log when a file from uploads directory is requested but a HTTP 404 returned?
  3. (You probably had, but) Have you (re)provisioned the server successfully (using ansible)?
### HEAD
* Officially support Ubuntu 20.04 (and default Vagrant to it) ([#1197](

### 1.6.0: November 5th, 2020

Am I on a dev version or at 1.6.0?

2: I’m seeing no error pop up in /srv/www/ when trying to access those files, and there’s nothing at all in access.log. The latest entry in error.log (some hours ago) is:
[error] 21107#21107: *861812 access forbidden by rule, client:, server:, request: "GET /.env HTTP/1.1", host: ""

3: I tried it in the past with the other clients, haven’t tried it now, I can give it a shot.

The latest Trellis release is v1.8.0 (
Though the master can also be used, hence the for finding out the exact version.

Hm, if it is reasonable enough for you, you could update to latest Trellis (master) as there were some permissions-related PRs merged in the meantime:

Trellis repository:

Also a full and successful reprovision can also fix these issues, ansible/Trellis can be re-applied as often as wished.

Thank you, I’ll try an update.

Well, I took a turn down idiot lane and accidentally rm -rf ~/ while trying to rm -rf ~/work/
So, now I have an indeterminate amount of missing files.
That being said, I reprovisioned using trellis 1.8.0 and nothing changed.

My nginx config is below:

Ansible managed

server {
  listen [::]:443 ssl http2;
  listen 443 ssl http2;
  access_log   /srv/www/ main;
  error_log    /srv/www/;
  root  /srv/www/;
  index index.php index.htm index.html;
  add_header Fastcgi-Cache $upstream_cache_status;

  # Specify a charset
  charset utf-8;

  # Set the max body size equal to PHP's max POST size.
  client_max_body_size 25m;

  # SSL configuration
  include h5bp/directive-only/ssl.conf;
  ssl_dhparam /etc/nginx/ssl/dhparams.pem;
  ssl_buffer_size 1400; # 1400 bytes to fit in one MTU

  add_header Strict-Transport-Security "max-age=0; includeSubDomains; ";
  include acme-challenge-location.conf;

  include includes.d/all/*.conf;
  include includes.d/*.conf;

  # Prevent PHP scripts from being executed inside the uploads folder.
  location ~* /app/uploads/.*\.php$ {
    deny all;
  # Prevent Blade and Twig templates from being accessed directly.
  location ~* \.(blade\.php|twig)$ {
    deny all;
  # composer
  location ~* composer\.(json|lock)$ {
    deny all;

  location ~* composer/installed\.json$ {
    deny all;

  location ~* auth\.json$ {
    deny all;

  # npm
  location ~* package(-lock)?\.json$ {
    deny all;

  # yarn
  location ~* yarn\.lock$ {
    deny all;

  # bundler
  location ~* Gemfile(\.lock)?$ {
    deny all;

  location ~* gems\.(rb|locked)?$ {
    deny all;
  location / {
    try_files $uri $uri/ /index.php?$args;
  include h5bp/directive-only/cache-file-descriptors.conf;
  include h5bp/directive-only/extra-security.conf;
  include h5bp/directive-only/x-ua-compatible.conf;
  include h5bp/location/cross-domain-fonts.conf;
  include h5bp/location/protect-system-files.conf;
  add_header Content-Security-Policy "frame-ancestors 'self'" always;

  # Conditional X-Frame-Options until is resolved
  set $x_frame_options SAMEORIGIN;
  if ($arg_customize_changeset_uuid) {
    set $x_frame_options "";
  add_header X-Frame-Options $x_frame_options always;

  # Prevent search engines from indexing non-production environments
  add_header X-Robots-Tag "noindex, nofollow" always;

  location ~ \.php$ {
    try_files $uri /index.php;

    include fastcgi_params;
    fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
    fastcgi_param DOCUMENT_ROOT $realpath_root;
    fastcgi_pass unix:/var/run/php-fpm-wordpress.sock;

# Redirect to https
server {
  listen [::]:80;
  listen 80;

  include acme-challenge-location.conf;

  include includes.d/all/*.conf;
  include includes.d/*.conf;

  location / {
    return 301 https://$host$request_uri;

This topic was automatically closed after 42 days. New replies are no longer allowed.