Apache / API behind NGINX Reverse Proxy - $remote_user printing

Dear all,

My issue: I implemented successfully FreshRSS behind NGINX reverse proxy. But following the discussion, I would like to go further and get the $remote_user printed in the nginx access.log when connected to the API. The recommended solution seems to look at “Authorization” and if it’s equal to “^GoogleLogin auth=([^/]+)” then modify accordingly the value of $remote_user. Unfortunately I don’t know how to do that!

I want to translate SetEnvIfNoCase “Authorization” “^GoogleLogin auth=([^/]+)” REMOTE_USER=$1 LOG_REMOTE_USER=$1 (apache language) into the nginx location block.

How I encountered the problem: FreshRSS Docker behind nginx-proxy Docker image.

Version of NGINX or NGINX adjacent software (e.g. NGINX Gateway Fabric): 1.29.3

Deployment environment: docker

Minimal NGINX config to reproduce your issue:

nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
# configuration file /etc/nginx/nginx.conf:

worker_processes  auto;

error_log  /var/log/nginx/error.log notice;
pid        /var/run/nginx.pid;


events {
    worker_connections  1024;
}


http {
    server_tokens off;
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    keepalive_timeout  65;

    #gzip  on;

    include /etc/nginx/conf.d/*.conf;
}

# configuration file /etc/nginx/mime.types:

types {
    text/html                                        html htm shtml;
    text/css                                         css;
    text/xml                                         xml;
    image/gif                                        gif;
    image/jpeg                                       jpeg jpg;
    application/javascript                           js;
    application/atom+xml                             atom;
    application/rss+xml                              rss;

    text/mathml                                      mml;
    text/plain                                       txt;
    text/vnd.sun.j2me.app-descriptor                 jad;
    text/vnd.wap.wml                                 wml;
    text/x-component                                 htc;

    image/avif                                       avif;
    image/png                                        png;
    image/svg+xml                                    svg svgz;
    image/tiff                                       tif tiff;
    image/vnd.wap.wbmp                               wbmp;
    image/webp                                       webp;
    image/x-icon                                     ico;
    image/x-jng                                      jng;
    image/x-ms-bmp                                   bmp;

    font/woff                                        woff;
    font/woff2                                       woff2;

    application/java-archive                         jar war ear;
    application/json                                 json;
    application/mac-binhex40                         hqx;
    application/msword                               doc;
    application/pdf                                  pdf;
    application/postscript                           ps eps ai;
    application/rtf                                  rtf;
    application/vnd.apple.mpegurl                    m3u8;
    application/vnd.google-earth.kml+xml             kml;
    application/vnd.google-earth.kmz                 kmz;
    application/vnd.ms-excel                         xls;
    application/vnd.ms-fontobject                    eot;
    application/vnd.ms-powerpoint                    ppt;
    application/vnd.oasis.opendocument.graphics      odg;
    application/vnd.oasis.opendocument.presentation  odp;
    application/vnd.oasis.opendocument.spreadsheet   ods;
    application/vnd.oasis.opendocument.text          odt;
    application/vnd.openxmlformats-officedocument.presentationml.presentation
                                                     pptx;
    application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
                                                     xlsx;
    application/vnd.openxmlformats-officedocument.wordprocessingml.document
                                                     docx;
    application/vnd.wap.wmlc                         wmlc;
    application/wasm                                 wasm;
    application/x-7z-compressed                      7z;
    application/x-cocoa                              cco;
    application/x-java-archive-diff                  jardiff;
    application/x-java-jnlp-file                     jnlp;
    application/x-makeself                           run;
    application/x-perl                               pl pm;
    application/x-pilot                              prc pdb;
    application/x-rar-compressed                     rar;
    application/x-redhat-package-manager             rpm;
    application/x-sea                                sea;
    application/x-shockwave-flash                    swf;
    application/x-stuffit                            sit;
    application/x-tcl                                tcl tk;
    application/x-x509-ca-cert                       der pem crt;
    application/x-xpinstall                          xpi;
    application/xhtml+xml                            xhtml;
    application/xspf+xml                             xspf;
    application/zip                                  zip;

    application/octet-stream                         bin exe dll;
    application/octet-stream                         deb;
    application/octet-stream                         dmg;
    application/octet-stream                         iso img;
    application/octet-stream                         msi msp msm;

    audio/midi                                       mid midi kar;
    audio/mpeg                                       mp3;
    audio/ogg                                        ogg;
    audio/x-m4a                                      m4a;
    audio/x-realaudio                                ra;

    video/3gpp                                       3gpp 3gp;
    video/mp2t                                       ts;
    video/mp4                                        mp4;
    video/mpeg                                       mpeg mpg;
    video/quicktime                                  mov;
    video/webm                                       webm;
    video/x-flv                                      flv;
    video/x-m4v                                      m4v;
    video/x-mng                                      mng;
    video/x-ms-asf                                   asx asf;
    video/x-ms-wmv                                   wmv;
    video/x-msvideo                                  avi;
}

# configuration file /etc/nginx/conf.d/default.conf:
# nginx-proxy
# Networks available to the container labeled "com.github.nginx-proxy.nginx-proxy.nginx" or the one running docker-gen
# (which are assumed to match the networks available to the container running nginx):
#     reverse_proxy
map $proxy_add_x_forwarded_for $proxy_x_forwarded_for {
    default $remote_addr;
    '' $remote_addr;
}
# If we receive X-Forwarded-Proto, pass it through; otherwise, pass along the
# scheme used to connect to this server
map $http_x_forwarded_proto $proxy_x_forwarded_proto {
    default $scheme;
    '' $scheme;
}
map $http_x_forwarded_host $proxy_x_forwarded_host {
    default $host;
    '' $host;
}
# If we receive X-Forwarded-Port, pass it through; otherwise, pass along the
# server port the client connected to
map $http_x_forwarded_port $_proxy_x_forwarded_port {
    default $server_port;
}
map $_proxy_x_forwarded_port $proxy_x_forwarded_port {
    default $_proxy_x_forwarded_port;
    '' $server_port;
}
# Include the port in the Host header sent to the container if it is non-standard
map $server_port $host_port {
    default :$server_port;
    80 '';
    443 '';
}
# If the request from the downstream client has an "Upgrade:" header (set to any
# non-empty value), pass "Connection: upgrade" to the upstream (backend) server.
# Otherwise, the value for the "Connection" header depends on whether the user
# has enabled keepalive to the upstream server.
map $http_upgrade $proxy_connection {
    default upgrade;
    '' $proxy_connection_noupgrade;
}
map $upstream_keepalive $proxy_connection_noupgrade {
    # Preserve nginx's default behavior (send "Connection: close").
    default close;
    # Use an empty string to cancel nginx's default behavior.
    true '';
}
# Abuse the map directive (see <https://stackoverflow.com/q/14433309>) to ensure
# that $upstream_keepalive is always defined.  This is necessary because:
#   - The $proxy_connection variable is indirectly derived from
#     $upstream_keepalive, so $upstream_keepalive must be defined whenever
#     $proxy_connection is resolved.
#   - The $proxy_connection variable is used in a proxy_set_header directive in
#     the http block, so it is always fully resolved for every request -- even
#     those where proxy_pass is not used (e.g., unknown virtual host).
map "" $upstream_keepalive {
    # The value here should not matter because it should always be overridden in
    # a location block (see the "location" template) for all requests where the
    # value actually matters.
    default false;
}
# Apply fix for very long server names
server_names_hash_bucket_size 128;
# Default dhparam
ssl_dhparam /etc/nginx/dhparam/dhparam.pem;
# Set appropriate X-Forwarded-Ssl header based on $proxy_x_forwarded_proto
map $proxy_x_forwarded_proto $proxy_x_forwarded_ssl {
    default off;
    https on;
}
gzip_types text/plain text/css application/javascript application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript;
log_format vhost escape=default '$host $remote_addr - $remote_user [$time_local] "$request" $status $body_bytes_sent "$http_referer" "$http_user_agent" "$upstream_addr" $ssl_protocol $ssl_cipher "$sent_http_content_type" $request_time';
access_log off;
    ssl_protocols TLSv1.3;
    ssl_ciphers ECDHE-ECDSA-AES256-GCM-SHA384;
    ssl_conf_command Ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256;
    ssl_prefer_server_ciphers off;
error_log /dev/stderr;
# HTTP 1.1 support
proxy_http_version 1.1;
proxy_set_header Host $host$host_port;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $proxy_connection;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_x_forwarded_for;
proxy_set_header X-Forwarded-Host $proxy_x_forwarded_host;
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
proxy_set_header X-Forwarded-Ssl $proxy_x_forwarded_ssl;
proxy_set_header X-Forwarded-Port $proxy_x_forwarded_port;
proxy_set_header X-Original-URI $request_uri;
# Mitigate httpoxy attack (see README for details)
proxy_set_header Proxy "";
server {
    server_name _; # This is just an invalid value which will never trigger on a real hostname.
    server_tokens off;
    access_log /var/log/nginx/access.log vhost;
    http2 on;
    listen 8080;
    listen 8443 ssl;
    ssl_session_cache shared:SSL:50m;
    ssl_session_tickets off;
    ssl_certificate /etc/nginx/certs/default.crt;
    ssl_certificate_key /etc/nginx/certs/default.key;
    location ^~ / {
        return 503;
    }
}
# xx/freshrss
upstream xx-1097b4b6ab1cd8d43e9d3d765fad295606b80016 {
    # Container: freshrss-docker-web-1
    #     networks:
    #         freshrss-docker_backend (unreachable)
    #         reverse_proxy (reachable)
    #     IPv4 address: 172.28.5.3
    #     IPv6 address: (none usable)
    #     exposed ports (first ten): 80/tcp
    #     default port: 80
    #     using port: 80
    server 172.28.5.3:80;
    keepalive 2;
}
server {
    server_name xx.fr;
    access_log /var/log/nginx/access.log vhost;
    listen 8080;
    # Do not HTTPS redirect Let's Encrypt ACME challenge
    location ^~ /.well-known/acme-challenge/ {
        auth_basic off;
        auth_request off;
        allow all;
        root /usr/share/nginx/html;
        try_files $uri =404;
        break;
    }
    location / {
        if ($request_method ~ (OPTIONS|POST|PUT|PATCH|DELETE)) {
            return 301 https://$host:84437$request_uri;
        }
        return 301 https://$host:8443$request_uri;
    }
}
server {
    server_name xx.fr;
    access_log /var/log/nginx/access.log vhost;
    http2 on;
    listen 8443 ssl;
    ssl_session_timeout 5m;
    ssl_session_cache shared:SSL:50m;
    ssl_session_tickets off;
    ssl_certificate /etc/nginx/certs/xx.crt;
    ssl_certificate_key /etc/nginx/certs/xx.key;
    ssl_dhparam /etc/nginx/certs/xx.dhparam.pem;
    ssl_stapling on;
    ssl_stapling_verify on;
    ssl_trusted_certificate /etc/nginx/certs/xx.chain.pem;
    set $sts_header "";
    if ($https) {
        set $sts_header "max-age=31536000";
    }
    add_header Strict-Transport-Security $sts_header always;
    include /etc/nginx/vhost.d/default;
    location /freshrss {
        proxy_pass http://xx-1097b4b6ab1cd8d43e9d3d765fad295606b80016;
        set $upstream_keepalive true;
        auth_basic "Restricted xx/freshrss";
        auth_basic_user_file /etc/nginx/htpasswd/xx_1097b4b6ab1cd8d43e9d3d765fad295606b80016;
        include /etc/nginx/vhost.d/xx_1097b4b6ab1cd8d43e9d3d765fad295606b80016_location;
    }
    include /etc/nginx/vhost.d/xx_9ae1aacb42439cb07010d9921db96191facc4f02_location_override;
    location / {
        return 418;
    }
}

# configuration file /etc/nginx/vhost.d/xx_1097b4b6ab1cd8d43e9d3d765fad295606b80016_location:
location /freshrss/ {
    # Copié collé depuis https://github.com/FreshRSS/FreshRSS/tree/edge/Docker#hosted-in-a-subdirectory
    # En commenté les lignes préconisées mais dont une version identique ou proche existe dans le bloc server par défaut
    proxy_pass http://172.28.5.3/;
    add_header X-Frame-Options SAMEORIGIN;
    add_header X-XSS-Protection "1; mode=block";
    proxy_redirect off;
    proxy_buffering off;
    #proxy_set_header Host $host;
    #proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-Prefix /freshrss/;
    #proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    #proxy_set_header X-Forwarded-Proto $scheme;
    #proxy_set_header X-Forwarded-Port $server_port;
    proxy_read_timeout 90;

    # Sécurisation du header https://securityheaders.com
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
    add_header Permissions-Policy "camera=(), microphone=(), geolocation=(), fullscreen=(self), payment=(), usb=()" always;
    add_header Referrer-Policy "no-referrer" always;

    # Support basicauth
    proxy_set_header Remote-User $remote_user;

    location /freshrss/api {
        proxy_pass http://172.28.5.3/api;
        proxy_set_header X-Forwarded-Prefix /freshrss/api;
        auth_basic off;

        # Copié collé depuis https://github.com/FreshRSS/FreshRSS/tree/edge/Docker#hosted-in-a-subdirectory
        # Forward the Authorization header for the Google Reader API.
	proxy_set_header Authorization $http_authorization;
        proxy_pass_header Authorization;

    }

}

# configuration file /etc/nginx/network_internal.conf:
# Only allow traffic from internal clients
allow 127.0.0.0/8;
allow 10.0.0.0/8;
allow 192.168.0.0/16;
allow 172.16.0.0/12;
allow fc00::/7; # IPv6 local address range
deny all;

# configuration file /etc/nginx/conf.d/my_proxy.conf:
proxy_request_buffering  off;
#server_tokens            off;

NGINX access/error log: No error

Edited january 10th:

When I added $http_authorization to log_format, I found the requested information:
myserver y.y.y.y - - [10/Jan/2026:09:14:09 +0000] "GET /freshrss/api/greader.php/reader/api/0/subscription/list?output=json HTTP/2.0" 200 3525 "-" "CapyReader (RSS Reader; https://capyreader.com/)" "x.x.x.x:80" TLSv1.3 TLS_AES_128_GCM_SHA256 "application/json; charset=UTF-8" 0.032 GoogleLogin auth=bob/0123456789012345678901234567890123456789

Interesting point is GoogleLogin auth=bob/0123456789012345678901234567890123456789

I would like to extract “bob” and show it as $remote_user variable
I tried this below (if part) without any success:

location /freshrss/api {
        proxy_pass http://x.x.x.x/api;
        proxy_set_header X-Forwarded-Prefix /freshrss/api;
        auth_basic off;

        # Copié collé depuis https://github.com/FreshRSS/FreshRSS/tree/edge/Docker#hosted-in-a-subdirectory
        # Forward the Authorization header for the Google Reader API.
        proxy_set_header Authorization $http_authorization;
        proxy_pass_header Authorization;

       if ($http_authorization ~ "GoogleLogin auth=([^\/]+)") {
           set $remote_user $1;
           }
       }

Can someone help me ?

1 Like

Hi @Bob-le-pirate - thank you for your patience with us, and for the clear effort you’ve been putting into figuring this out.

Someone more technical than me will chime in soon, but in the meantime, high-level pointers…

  1. Create a new variable with map{}
  2. Write a custom log_format to include the new variable in the conventional $remote_user location

To add to what @heo said:

  1. It’s usually preferrable to use map instead of if. if has a few processing quirks.
  2. Double check your NGINX regex. It uses PCRE and I know I have struggled getting the regex to match just fine in the past :slight_smile:

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.