#501·Nginx

feature: adding a configuration flag to set whether NGiNX should decode percent-encoded characters in URI or not

Author: mikyllCreated Feb 7, 2025Updated Sep 17, 2026
Labelsfeatureneeds-analysis

[!NOTE]

I'm not a security expert and I don't know if that feature might generate vulnerabilities or anything similar (e.g. path traversal attacks). If that's the case, you can directly close or delete the issue, but I'd love to be redirected to some relevant documentation on the topic and/or to receive some guidance on how to approach this task in the correct way

Describe the feature you'd like to add to nginx

I'd like to introduce a configuration flag named decode_percent_characters (or something similar):

  • by default set to on, so that it doesn't change NGiNX default behaviour;
  • similarly to merge_slashes, it's passed to ngx_http_parse_complex_uri();
  • if set to off, NGiNX doesn't replace percent-encoded characters in request URIs, with their respective decoded values (i.e. %2f doesn't get decoded to / if we set decode_percent_characters off; in nginx.conf);

Example

Show/Hide example

Suppose we compiled Nginx from source (files located at /usr/local/nginx/) and we are serving 2 pages:

  • /usr/local/nginx/html/path%2fslash/index.html:

    xml
    Path: path%2fslash/
  • /usr/local/nginx/html/path/slash/index.html:

    xml
    Path: path/slash/
Before this feature

Nginx config file (/usr/local/nginx/conf/nginx.conf):

conf
worker_processes  1;

events {
  worker_connections  1024;
}

http {
  server {
    listen       80;
    server_name  localhost;
  }
}

Test requests:

  1. Encoded slash:

    bash
    $ curl localhost/path%2fslash/
    Path: path/slash/
  2. Slash:

    bash
    $ curl localhost/path/slash/
    Path: path/slash/

Logs (/usr/local/nginx/logs/access.log):

log
127.0.0.1 - - [28/Apr/2026:15:42:59 +0200] "GET /path/slash/ HTTP/1.1" 200 18 "-" "curl/8.5.0"
127.0.0.1 - - [28/Apr/2026:15:42:59 +0200] "GET /path/slash/ HTTP/1.1" 200 18 "-" "curl/8.5.0"

NB: It always returns the page at path/slash (%2f is automatically decoded to / before matching the route).

With new feature
conf
worker_processes  1;

events {
  worker_connections  1024;
}

http {
  decode_percent_characters off;
  
  server {
    listen       80;
    server_name  localhost;
  }
}

Test requests:

  1. Encoded slash:

    bash
    $ curl localhost/path%2fslash/
    Path: path%2fslash/
  2. Slash:

    bash
    $ curl localhost/path/slash/
    Path: path/slash/

Logs (/usr/local/nginx/logs/access.log):

log
127.0.0.1 - - [28/Apr/2026:15:43:54 +0200] "GET /path%2fslash/ HTTP/1.1" 200 20 "-" "curl/8.5.0"
127.0.0.1 - - [28/Apr/2026:15:43:57 +0200] "GET /path/slash/ HTTP/1.1" 200 18 "-" "curl/8.5.0"

NB: it serves the correct pages.

Describe the problem this feature solves

With this feature, we would be able to serve pages containing percent-encoded characters in their path (e.g. /usr/local/nginx/html/path%2fslash/index.html).

Additionally, when NGiNX is used as a reverse proxy, such as when it's distributed via OpenResty and used as an API Gateway (e.g. APISIX, Kong, etc.), we can match routes that have path parameters containing percent-encoded characters, and delegate their decoding to upstreams/other web servers. Notice that many popular web frameworks already support percent-encoded characters in path parameters:

Technology Supports %-encoded chars Usage Docs
Django (Python) Using <path:id> Django - Path Converters
Fastapi (Python) Using {id:path} Fastapi - Path Convertor
Flask (Python) Using <path:id> Flask - Variable Rules
Express (JS) Using :id (default) Express - Route Parameters
Fastify (JS) Using :id (default) Fastify - URL Building
Actix Web (Rust) Using {id} (default) Actix - Resource Pattern Syntax
ASP.NET Core (C#) Using {id} (default) ASP.NET - Route Templates
Spring (Java) By default requests containing %2F in path parameters return 400: Bad Request, but Tomcat can be configured to allow them Spring MVC - URI Patterns
NGiNX (C) - -

Additional context

Some References

Feature Implementation

I already implemented two versions of this feature in a fork:

APISIX Example

With this feature, we could make APISIX correctly match routes containing %-encoded characters in path parameters (when using router radixtree_uri_with_parameters, see APISIX Docs | Router), by simply setting the following configuration in /apisix/path/conf/config.yaml:

yaml
nginx_config:
  http_configuration_snippet: |
    decode_percent_characters off;

OpenResty

Maybe there's a simpler way to handle this via OpenResty ngx_http_lua_module?