> ## Documentation Index
> Fetch the complete documentation index at: https://vytral-dependabot-npm-and-yarn-development-95cc887cce.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Proxy modes

> Compare Caddy, external reverse proxy, and local-only modes for self-hosted Harly, and trust upstream proxies for correct client IPs.

The installer asks which proxy mode to use. The selected mode is stored in `harly.config.json`, and `doctor` validates only its expected services.

## After reading this

* You will know the three proxy modes and when to use each
* You will know which ports each mode publishes
* You will know how to point an existing reverse proxy at Harly

## Available modes

| Mode       | `COMPOSE_PROFILES` | Publishes                                 | Use when                                           |
| ---------- | ------------------ | ----------------------------------------- | -------------------------------------------------- |
| `caddy`    | `proxy`            | 80/443                                    | You want Harly to manage TLS automatically         |
| `external` | (none)             | `127.0.0.1:$HARLY_PORT` only              | Nginx, Apache, or Traefik already owns 80/443      |
| `local`    | (none)             | Same loopback port, `http://` URL, no TLS | Local development or an internal-only installation |

Caddy is never started in `external` or `local` mode.

## Using an external reverse proxy

If another proxy already owns ports 80 and 443, keep it and select `external` mode during install. Forward the hostname to `127.0.0.1:3000`.

Example Nginx server block:

```nginx theme={null}
server {
    listen 443 ssl;
    server_name careers.example.com;

    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        # Must match the TRUSTED_PROXY_IPS value Harly receives.
        proxy_set_header X-Forwarded-Peer 127.0.0.1;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}
```

Point your existing certificate management (Certbot, an ACME client, or a load balancer) at the same hostname; Harly does not manage TLS in this mode.

<Note>
  The Nginx block above is a standard reverse-proxy pattern, not a file shipped by Harly. Adapt it to your existing proxy configuration.
</Note>

### Trust your reverse proxy for client IPs

By default, Harly reads the client IP from the rightmost hop in `X-Forwarded-For`. In `external` mode that hop is your reverse proxy, not the end user. Workspace IP allowlists, per-IP rate limits, and audit log `ipAddress` fields will all see the proxy's address instead of the real client until you tell Harly which peer to trust.

Set `TRUSTED_PROXY_IPS` in `.env` to the peer address (or comma-separated addresses) that Harly's container sees when the proxy forwards a request:

```dotenv theme={null}
# Single reverse proxy on the same host
TRUSTED_PROXY_IPS=127.0.0.1

# Multiple proxies or a load balancer pool
TRUSTED_PROXY_IPS=10.0.0.5,10.0.0.6
```

When the immediate peer matches one of these values, Harly uses the leftmost address in `X-Forwarded-For` as the client IP. Your reverse proxy must overwrite `X-Forwarded-Peer` with an operator-controlled marker that matches the value Harly receives for that proxy, as in the Nginx example above; never pass a client-supplied value through unchanged. If Harly runs behind a container or another network hop, replace `127.0.0.1` with the peer address Harly sees and use the same value in `TRUSTED_PROXY_IPS`. When the peer does not match, Harly falls back to the rightmost hop so a malicious client cannot spoof its address by injecting the header.

Guidelines:

* Use the address Harly actually receives the connection from, not the public IP of the proxy. Behind Docker with a host-network Nginx, that is typically `127.0.0.1` or the Docker bridge gateway.
* If several load balancers can each forward directly to Harly, list all of them, comma-separated.
* Behind a CDN like Cloudflare in front of your own reverse proxy, trust the reverse proxy's address here and let the CDN handle client IP resolution in your proxy configuration.
* Leave `TRUSTED_PROXY_IPS` unset in `caddy` mode. The bundled Caddy proxy adds exactly one forwarding hop, so the rightmost-hop default already resolves to the real client.
* After changing the value, restart the app container so the new environment is picked up.

## Troubleshooting

### `doctor` reports the wrong services as unhealthy

**Symptom:** `doctor` checks for Caddy even though you selected `external` mode.
**Cause:** `harly.config.json` still records the previous mode.
**Solution:** re-run the installer's management menu (`npx @harly/cli`) and confirm the proxy mode, or edit `harly.config.json` directly to match your actual topology.

### 502 from the external proxy

**Symptom:** the reverse proxy returns a 502 for every request.
**Cause:** Harly is not listening on `127.0.0.1:$HARLY_PORT`, or the proxy is forwarding to the wrong port.
**Solution:** confirm `docker compose ps` shows the app container healthy, then check `HARLY_PORT` in `.env` matches the proxy's upstream port.

## Related pages

<CardGroup cols={2}>
  <Card title="Self-hosting overview" icon="server" href="/self-hosting/overview">
    VPS requirements and DNS setup.
  </Card>

  <Card title="Operations" icon="wrench" href="/self-hosting/operations">
    Health checks, the scheduler, backups, and upgrades.
  </Card>
</CardGroup>
