Skip to content

Nginx Proxy Manager

Nginx Proxy Manager writes each proxy host’s log in its own proxy format. That format has no request time, upstream time, protocol, remote user or raw request line, so GeoMetrikks doesn’t tail it live. Only import-logs reads it, as Importing NPM’s existing logs describes. NPM can write a second log next to its own, though, through its custom nginx configuration files. Point that second log at the JSON format from Nginx setup. NPM’s own logs stay as they are.

The /data/... paths below are inside the NPM container. With the usual ./data:/data mount, /data/nginx/custom/http_top.conf is ./data/nginx/custom/http_top.conf on the host.

  1. Copy the log_format geometrikks_json block from Nginx setup into /data/nginx/custom/http_top.conf. NPM includes this file at the top of the http block, before it loads the proxy hosts. If the file already exists, for example with the realip lines from docs/proxy-setup.md, append the block. Define it once.

  2. Turn the log on. For every proxy host, put this line in /data/nginx/custom/server_proxy.conf:

    access_log /data/logs/geometrikks_access.log geometrikks_json;

    NPM includes that file in every proxy host’s server block, so hosts you add later log there too. To log only some hosts, leave server_proxy.conf alone and paste the same line into each host’s Advanced tab, under Custom Nginx Configuration. Pick one of the two for a given host. With both, nginx writes every request twice.

    Keep the JSON in its own file. Pointing it at NPM’s proxy-host-*_access.log puts two formats in one file. Don’t put access_log off; in front of the line either: off cancels every access_log at that level, this one included.

  3. Check the config and reload nginx inside the NPM container:

    Terminal window
    docker exec <npm-container> sh -c 'nginx -t && nginx -s reload'

    Open one of your sites, and its requests show up in data/logs/geometrikks_access.log.

  4. Point GeoMetrikks at the file in .env:

    ACCESS_LOG_DIR=/path/to/npm/data/logs
    LOGPARSER_LOG_PATHS=/var/log/access/geometrikks_access.log
    LOGPARSER_LOG_FORMATS=geometrikks-json

    Run docker compose up -d to recreate the container with the new mount. docker compose restart keeps the old one.

Every proxy host shares the one file. Each line carries host, so the host filter still tells them apart. NPM’s logrotate rule matches /data/logs/*_access.log, so this file rotates weekly along with NPM’s own logs.

What the log leaves out:

  • Cache Assets. On a host with Cache Assets on, NPM serves CSS, JS, images and fonts from a location that sets access_log off. Those requests never reach any log, so GeoMetrikks never sees them. NPM adds the same location inside each custom location on that host. Turn Cache Assets off on the hosts where you want that traffic counted.
  • Other host types. server_proxy.conf covers proxy hosts only. Redirection hosts, 404 hosts and the default site don’t write to this log. NPM has separate snippet files for redirection and 404 hosts (server_redirect.conf, server_dead.conf), but this setup has only been tested on proxy hosts. TCP and UDP streams are not HTTP and this format doesn’t apply to them.
  • Visitor address. Behind Cloudflare or another proxy, $remote_addr is that proxy’s address, and the map shows its datacenter. Add the realip lines from docs/proxy-setup.md to the same http_top.conf.

import-logs reads NPM’s own proxy-host-<id>_access.log files and their rotated copies. LOGPARSER_LOG_FORMATS=npm fails at startup, so these logs are for backfill only.

NPM keeps writing its own logs after the JSON log is on, so every request since then is in both. Pass --before with the time GeoMetrikks started reading the JSON log, and the import stops there. If you started GeoMetrikks right after the reload in step 3, that is the first line of the oldest geometrikks_access.log file on disk:

Terminal window
ls data/logs/geometrikks_access.log*
# open the highest-numbered one; zcat -f reads plain and .gz files
zcat -f data/logs/geometrikks_access.log.2.gz | head -n 1
# {"client_ip":"...","timestamp":"2026-10-01T14:03:12+02:00",...}

GeoMetrikks starts reading a log at its end, so it never ingested what the JSON log wrote before GeoMetrikks started. If GeoMetrikks started later, use the time of the oldest request on the Access Logs page instead, or those requests stay missing.

Import the rotated .gz copies only:

Terminal window
docker compose exec -u geometrikks app sh -c \
'litestar import-logs --before 2026-10-01T14:03:12+02:00 /var/log/access/proxy-host-*_access.log.*.gz'

The sh -c makes the container expand the glob against its own paths. The current proxy-host-<id>_access.log is left out because it is still growing: after NPM rotates it, it comes back as .1.gz with a new checksum, and importing that would add its lines a second time. If the JSON log is less than a week old, the current files still hold requests from before the cutoff. Wait for NPM’s next weekly rotation, then import.

NPM’s other access logs import the same way:

  • redirection-host-<id>_access.log for redirection hosts
  • dead-host-<id>_access.log for 404 hosts
  • fallback_http_access.log for requests that matched no host

default-host_access.log is in nginx’s combined format and imports as nginx, without host or timings. Legacy nginx format lists what that format lacks.

NPM rotates these logs weekly and keeps four compressed copies, proxy-host-<id>_access.log.1.gz to .4.gz. That puts about five weeks of history on disk, and each rotation deletes the oldest week. Import soon after turning on the JSON log.

A later run skips the archives it already imported. NPM renumbers them but doesn’t change their content, so their checksums stay the same. If the cutoff turns out to be too early, run the same command with the later one. GeoMetrikks stores each file’s cutoff and imports only the lines in between. An earlier cutoff is refused, because the rows past it are already written.

Imported NPM rows have no response time, upstream time, protocol or remote user. The map, host filter, status codes, URLs, referrers, user agents and bytes are unaffected.