How to Implement Multiple Sub_filter Rules in Nginx Reverse Proxy for Dynamic Content Replacement


2 views

When setting up a reverse proxy in Nginx, the sub_filter directive is commonly used for replacing strings in proxied content. However, a significant limitation exists: you can only specify one sub_filter per location block. This becomes problematic when you need multiple replacements.

location / {
    proxy_pass http://upstream;
    sub_filter 'Original' 'Replacement';
    sub_filter_once off;
}

Here are three practical approaches to overcome this limitation:

1. Using Multiple Location Blocks

Create separate location blocks for different paths, each with its own sub_filter:

location /path1/ {
    proxy_pass http://upstream/path1/;
    sub_filter 'String1' 'New1';
    sub_filter_once off;
}

location /path2/ {
    proxy_pass http://upstream/path2/;
    sub_filter 'String2' 'New2';
    sub_filter_once off;
}

2. Lua Module for Advanced Replacement

For more complex scenarios, consider using Nginx's Lua module:

location / {
    proxy_pass http://upstream;
    header_filter_by_lua_block {
        ngx.arg[1] = ngx.arg[1]:gsub("Old1", "New1")
        ngx.arg[1] = ngx.arg[1]:gsub("Old2", "New2")
    }
    body_filter_by_lua_block {
        ngx.arg[1] = ngx.arg[1]:gsub("Old1", "New1")
        ngx.arg[1] = ngx.arg[1]:gsub("Old2", "New2")
    }
}

3. Combining Sub_filter with Regular Expressions

For simple cases, you might combine multiple replacements into one pattern:

location / {
    proxy_pass http://upstream;
    sub_filter 'Old1|Old2' '${1}New';
    sub_filter_once off;
}

Each additional replacement impacts performance. For high-traffic sites:

  • Cache processed responses when possible
  • Minimize the number of replacements
  • Consider doing replacements at the application level instead

Always verify your replacements work as expected:

curl -v http://yourproxy | grep -i "ReplacementString"

When setting up Nginx as a reverse proxy, the sub_filter directive allows content modification during proxying, but comes with a significant constraint: only one replacement rule can be defined per location block. This becomes problematic when multiple string substitutions are needed.

location / {
    proxy_pass http://upstream;
    sub_filter 'Original' 'Replaced';
    sub_filter_once off;
    # Cannot add another sub_filter here
}

1. Using Multiple Location Blocks

Create separate location blocks for different path patterns, each with its own substitution rule:

location /section1/ {
    proxy_pass http://upstream;
    sub_filter 'foo' 'bar';
    sub_filter_once off;
}

location /section2/ {
    proxy_pass http://upstream;
    sub_filter 'baz' 'qux';
    sub_filter_once off;
}

2. Lua Module for Advanced Processing

For complex scenarios, Nginx's Lua module provides more flexibility:

location / {
    proxy_pass http://upstream;
    header_filter_by_lua_block {
        ngx.header.content_length = nil
    }
    body_filter_by_lua_block {
        local body = ngx.arg[1]
        if body then
            body = body:gsub("pattern1", "replacement1")
            body = body:gsub("pattern2", "replacement2")
            ngx.arg[1] = body
        end
    }
}

3. Using the Perl Module

For environments where Perl is available, you can leverage Nginx's perl module:

location / {
    proxy_pass http://upstream;
    perl_modules perl/lib;
    perl_require replace.pm;
    perl_set $processed_content 'MyReplace::filter';
}

With replace.pm containing:

package MyReplace;

sub filter {
    my $r = shift;
    my $content = $r->request_body;
    $content =~ s/old1/new1/g;
    $content =~ s/old2/new2/g;
    return $content;
}

1;

When implementing multiple replacements:

  • The Lua solution offers best performance for most cases
  • Multiple location blocks work well when content is naturally segmented
  • Perl module provides maximum flexibility but with higher overhead

For extremely complex scenarios, consider processing the content externally:

location / {
    proxy_pass http://upstream;
    proxy_set_header Accept-Encoding "";
    sub_filter '' ''; # Dummy filter to enable processing
    sub_filter_once off;
    
    # Additional processing through fastcgi or proxy to processing service
}