> ## Content Index
> Fetch the complete content index at: https://blog.coopnet.us/llms.txt
> Use this file to discover other available public pages before exploring further.

# Nexterm logged my reverse proxy instead of the client
- URL: https://blog.coopnet.us/nexterm-real-client-ip/
- Published: 2026-10-29T22:26:45.000Z
- Updated: 2026-10-31T16:52:56.000Z
- Description: Nexterm logged my reverse proxy as the source of every session because Express wasn't trusting the proxy. I patched it with a bind mounted index.js. Current releases read TRUST_PROXY, so drop the patch and set that to the proxy's own address, not true.
- Author: Renstur
- Tags: services, nexterm, reverse-proxy, field-notes

I put Nexterm behind my reverse proxy and later went through its audit log. Every session came from the same address. It was the proxy.

That's not much of an audit log for a tool whose whole job is opening shells on other machines.

## What the log was recording

I was running Nexterm from the all in one image, `nexterm/aio:latest`. The web UI listens on 6989 and the engine's control port is 7800\. The web port was published only on the host's overlay address, and everyone went through the proxy at nexterm.example.lab:

```yaml
services:
  nexterm:
    image: nexterm/aio:latest
    ports:
      - "100.64.x.x:6989:6989"
    volumes:
      - nexterm_data:/app/data

```

The server is a Node and Express app under `/app/server`. Express takes `req.ip` from the TCP connection unless it's told to trust a proxy. The build I had never told it. So every login came from the last hop, and the last hop is always the proxy.

The proxy was already sending `X-Forwarded-For`. The fix below proves that. Nothing in that build read it.

## No setting to flip, at the time

I went looking for an environment variable first. The image I pulled didn't have one. No `TRUST_PROXY`, nothing in the compose examples, and no `app.set("trust proxy", ...)` anywhere in the server code. I hadn't pinned a version, so I can't tell you which release that was.

## Check your version first

Current Nexterm has the setting. `v1.2.2-BETA`, released in July 2026, reads a `TRUST_PROXY` environment variable and hands it to Express. It takes `true`, a hop count, or a list of addresses, and it logs a warning if you use `true`. On that release or newer, skip my patch and set the variable:

```yaml
    environment:
      TRUST_PROXY: "<proxy address>"

```

The rest of this is what I did before that existed, and why it's worth undoing.

## One line, bind mounted

The fix was one line in `/app/server/index.js`, right after the app is created:

```js
const app = expressWs(express()).app;
app.set("trust proxy", true);

```

I didn't want to build my own image for one line. So I copied the image's `index.js` out to the host, added the line, and mounted it back over the original, read only.

Copy the file out of a running container:

```sh
docker cp <container>:/app/server/index.js /srv/nexterm/index.js

```

Then mount it back in:

```yaml
    volumes:
      - nexterm_data:/app/data
      - /srv/nexterm/index.js:/app/server/index.js:ro

```

After a recreate, new sessions in the audit log showed real client addresses.

One side note from the same container. The database is at `/app/data/nexterm.db`, but the storage path is relative to `/app`. Anything you run against it by hand needs that working directory:

```sh
docker exec -w /app <container> node <script>

```

## What the shortcut costs

That mounted file is the whole upstream `index.js` plus one line. When the image updates, the copy doesn't. The container ends up running an old entry point against new code.

Sometimes that would break loudly. The quiet case is worse. Pull a release that has `TRUST_PROXY`, keep the old mount, and the older file replaces the new handling. Set the variable and nothing happens.

So as long as a mount like that is in place, every update means pulling the new image, diffing its `index.js` against yours, and carrying the line forward before you recreate:

```sh
docker run --rm --entrypoint cat nexterm/aio:latest /app/server/index.js > /tmp/index.upstream.js
diff /tmp/index.upstream.js /srv/nexterm/index.js

```

The diff should show one added line. Anything more and you're behind. If upstream already has `TRUST_PROXY`, drop the mount.

## What the log showed after

Real client addresses, with one catch. A public or home address only showed up when the client came in over the internet through the proxy. Devices on the overlay showed their 100.64.x.x address instead. Both are right. They're different paths in.

## What I checked and what I didn't

- seen: every audit entry showed the proxy before the patch
- seen: no `trust proxy` call anywhere in that build's server code
- seen: real client addresses on new sessions after the recreate
- not checked: which Nexterm release I was on, or which release first added `TRUST_PROXY`

## What I'd tell myself

`trust proxy` set to `true` believes whatever `X-Forwarded-For` says. That only works if nothing can reach the app port except the proxy. Binding the port to one network keeps it off the others. It doesn't keep it away from anything else on that same network, and any machine there could send a fake header straight to the port.

A hop count of 1 doesn't help there. The README suggests 1 for a single Nginx or Traefik, which is fine when the proxy is the only way in. Express's docs warn that if a client can reach the app over a shorter path than the hop count assumes, it can supply any value it likes. A machine hitting the app port directly is that shorter path.

Trusting the proxy's own address holds up. Express only reads the header when the connection actually comes from the proxy, and everyone else gets logged as themselves. That's the one to set, through `TRUST_PROXY` this time instead of a patched file.