Bug / Question: valid_referers in a custom vhost location causes image requests to fail (404) when using nginx-proxy

Author: kev-masCreated Jul 18, 2026Updated Jul 18, 2026

Summary

I'm trying to implement hotlink protection in nginx-proxy so that every virtual host automatically allows requests originating from its own domain without having to hardcode each hostname.

My expectation was that using:

nginx
valid_referers none blocked server_names;

inside a custom location would use the current virtual host's server_name values, allowing me to reuse the same configuration for every hosted site.

Instead, image requests never reach the upstream and return 404s.

I'm wondering whether this is expected behavior, whether I'm missing something, or whether there is a recommended way of achieving this directly on the reverse proxy.


Environment

  • nginx-proxy image: nginxproxy/nginx-proxy:1.11.6
  • nginx version: 1.31.3
  • Reverse proxy in front of dedicated backend NGINX containers (one backend per WordPress site)

The generated server block looks essentially like this:

nginx
server {
    server_name mydomain.com;

    include /etc/nginx/vhost.d/mydomain.com;

    location / {
        proxy_pass http://mydomain.com;
        set $upstream_keepalive true;
    }
}

The custom configuration is injected via:

/etc/nginx/vhost.d/mydomain.com

What I'm trying to achieve

Ideally I'd like to have one reusable configuration like:

nginx
location ~* \.(?:gif|png|jpe?g|webp)$ {
    valid_referers none blocked server_names;

    if ($invalid_referer) {
        return 403;
    }
}

so I don't have to list every hosted domain individually.


What I tested

1. Explicit domains

This works correctly:

nginx
location ~* \.(?:gif|png|jpe?g|webp)$ {
    valid_referers
        none
        blocked
        mydomain.com
        *.mydomain.com;

    if ($invalid_referer) {
        return 403;
    }
}

2. Using server_names

Replacing the explicit domains with:

nginx
valid_referers none blocked server_names;

causes image requests to fail.

The browser reports a 404.


3. Debugging $invalid_referer

I temporarily replaced the location with:

nginx
return 200 "host=$host
server_name=$server_name
referer=$http_referer
invalid=$invalid_referer
";

Using:

bash
curl \
  --referer https://www.mydomain.com \
  https://www.mydomain.com/wp-content/themes/twentytwentyfive/screenshot.png

returned:

host=www.mydomain.com
server_name=www.mydomain.com
referer=https://www.mydomain.com
invalid=

which suggests valid_referers itself is working correctly.


4. Browser request

When loading the image from the WordPress admin page the request contains:

Host: www.mydomain.com
Referer: https://www.mydomain.com/wp-admin/themes.php
Sec-Fetch-Site: same-origin

So the request appears to be a valid same-origin request.


5. nginx -T

Inspecting the generated configuration showed that my custom file is included at the server level:

nginx
server {

    include /etc/nginx/vhost.d/mydomain.com;

    location / {
        proxy_pass http://mydomain.com;
    }
}

Therefore my regex location becomes a sibling of the generated location /.


What I eventually discovered

It appears that the regex location:

nginx
location ~* \.(?:gif|png|jpe?g|webp)$ {
    ...
}

takes precedence over the generated proxy location.

Since my custom location does not contain a proxy_pass, the request never reaches the upstream backend and instead results in a 404.

This seems to explain why the referer check itself succeeds while the image is never served.


Other approaches I tried

I also attempted to move the referer check into the generated location /, but discovered that:

nginx
if (...) {
    valid_referers ...
}

is invalid because valid_referers is not permitted inside an if block.


Current workaround

For now I've moved the hotlink protection to the backend NGINX instance that serves static files.

Each backend serves exactly one website, so I template the configuration using the official NGINX Docker image's built-in envsubst support.

For example:

nginx
valid_referers
    none
    blocked
    ${HOTLINK_DOMAIN}
    *.${HOTLINK_DOMAIN};

with:

HOTLINK_DOMAIN=mydomain.com

This works well, but it means the hotlink protection is implemented on every backend instead of centrally on the reverse proxy.


Question

Is there a supported way to implement this kind of reusable hotlink protection directly in nginx-proxy?

Specifically:

  • Is there a way to create a custom image location that still inherits the generated proxy_pass?
  • Is there a recommended way to inject valid_referers into the generated location / without overriding it?
  • Is this simply a limitation of the current template architecture?
  • Has anyone implemented hotlink protection centrally on nginx-proxy while keeping the configuration reusable across many virtual hosts?

I found the _location include mechanism documented for injecting directives into generated locations, but in my case I specifically wanted to protect only image requests without duplicating the generated proxy configuration, and I couldn't find a way to achieve that.