---
title: Litmus Forward Proxy
slug: solutions/litmus-forward-proxy
docTags: 
createdAt: 2026-08-12T00:03:02.216Z
---

## Overview

The Forward Proxy is a containerized HTTP/HTTPS proxy with a web UI. Machines on a restricted network route outbound traffic through it instead of reaching the internet directly. From the browser you set which source networks may connect, which domains are blocked, and see what traffic is flowing.

The engine is Squid. Every change is validated before it is saved, so a bad edit cannot take the proxy offline.

Use it to give a plant network one controlled exit point: a single host allowed outbound at the firewall, only approved source networks permitted, every request logged, and repeated downloads served from a local cache.

## Getting Started

Run on any LitmusEdge device:

```bash
docker run -d --name forwardproxy --restart unless-stopped -p 3128:3128 -p 3130:3130 -v squid-conf:/etc/squid -v squid-logs:/var/log/squid -v squid-cache:/var/spool/squid -v viewer-data:/var/lib/squidviewer -e UI_USERNAME=admin -e UI_PASSWORD=<your-password> us-docker.pkg.dev/litmus-customer-facing/litmus-solutions/forwardproxy:0.5.0
```

- Navigate to the `{edgeUrl}:3130` and sign in with the username and password you set
- Clients point at `{edgeUrl}:3128`, which is the proxy itself

Notes on the command above:

- **Set a real password.** There is no default, and an empty `UI_PASSWORD` starts the UI unauthenticated
- **Pin the version tag.** An orchestrator that caches `latest` will serve an older build and report success
- **Keep all four volumes**, or rules, logs, and the password are lost when the container is recreated
- **Add&#x20;**`--platform linux/amd64` on an ARM host
- Container ports are fixed at 3128 and 3130. If Litmus Edge maps them itself, use the host ports from the container detail view

## How to Route Traffic Through the Proxy

### Step 1: Allow Your Client Networks

On the **Rules** page, add the networks permitted to use the proxy, then click **Reload Squid**. The shipped configuration allows `10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`, and `127.0.0.1/32`, and clients outside those ranges are refused with `403`. A CIDR network or a single address is accepted, IPv4 or IPv6.

### Step 2: Pointing Litmus Edge Itself at the Proxy

Follow [Configure an HTTPs Proxy](docId\:bAYKOU6xOs_I8N9h1uiGB): navigate to **System > Network**, select the **Proxy** tab, click **Add Proxy** on the tile for each component you need (Device Management, Authentication, Licensing), and enter `http://<edge-ip>:3128`.

Two related behaviors: Litmus Edge Manager registration does a raw TLS dial that ignores the proxy, so it needs one-time direct reachability to the LEM, and WireGuard remote access uses UDP port 51820 and cannot traverse an HTTP proxy.

### Step 3: Verify

```bash
curl -x http://<edge-ip>:3128 http://example.com
curl -x http://<edge-ip>:3128 https://example.com
```

Both should return the page, and both should appear on the **Logs** page. A `TCP_DENIED/403` line means the proxy received the request and refused it, almost always because the client address is not in an allowed network.

### Step 4: Block Domains (Optional)

On the **Rules** page, add domains the proxy should refuse even for an allowed client, then click **Reload Squid**. Paste a full URL and the domain is extracted for you. A leading dot matches subdomains, so `.example.com` blocks everything beneath it.

## Other Deployment Options

- **Docker Compose**: `cd docker-compose`, `cp .env.example .env`, set `UI_PASSWORD`, then `docker compose up -d`. `.env` holds `UI_USERNAME`, `UI_PASSWORD`, `SQUID_PUBLISH_PORT`, and `UI_PUBLISH_PORT`
- **Air-gapped host**: `docker load -i forwardproxy.tar.gz` on the target. The UI fetches no external assets, so it works fully offline
- **LEM Marketplace**: upload `forwardproxy.tar.gz` to the LEM Docker registry, import the listing under **Features > Marketplace**, then deploy supplying `UI_PASSWORD`

## Web UI

| Page         | What it does                                                                                                                                                               |
| ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Rules**    | Allowed source networks and blocked domains                                                                                                                                |
| **Config**   | Full `squid.conf` editor. **Validate** parses, **Save** writes only if it parses, **Reload Squid** applies. Also sets the configuration path, log path, and proxy address. |
| **Stats**    | Requests, bytes, cache hit ratio, top domains, methods, and status codes, over the last 20,000 log lines                                                                   |
| **Logs**     | Live tail of the access log, last 200 lines by default                                                                                                                     |
| **Password** | Set or change the sign-in password                                                                                                                                         |

A configuration Squid would reject is never written. If a reload fails, the page shows Squid's first fatal line and the proxy keeps serving its previous configuration.

A password set in the UI is stored hashed and takes precedence over `UI_PASSWORD`, so an operator can secure a deployment without touching the container. Changing it signs out every session, including your own.

## Troubleshooting

- **Connection refused**: Wrong host or port. Confirm the published port with `docker ps`
- **Request denied with 403**: The client address is outside the allowed networks. Add it on the Rules page, then Reload Squid. NAT changes the source address, so take it from the log line
- **One site denied, others work**: The domain is blocked, or the site uses a port other than 80 or 443
- **Web UI not reachable**: Run `docker exec forwardproxy supervisorctl -c /etc/supervisor/conf.d/supervisord.conf status` and expect `squid`, `viewer`, and `logrotate` all `RUNNING`
- **Changes saved but behavior unchanged**: Click **Reload Squid**
- **Locked out of the UI**: Recreate the container with a new `UI_PASSWORD` and remove the settings volume, since a stored password takes precedence
- **Rules or logs lost after redeploy**: A volume was not mounted

To separate a client problem from a proxy problem, test from the proxy host itself. A `200` means the proxy works and the problem is between the client and the proxy:

```bash
docker exec forwardproxy curl -s -o /dev/null -w '%{http_code}\n' -x http://127.0.0.1:3128 http://example.com
```

## Security Considerations

The web UI is an administrative tool for a trusted operator on a trusted network.

- **Do not expose the proxy port to the internet.** An open forward proxy will be found and abused
- **Do not expose the UI port outside a trusted network.** Bind it to localhost and reach it over SSH or a VPN, or put it behind an authenticating TLS reverse proxy
- **Terminate TLS in front of the UI.** The credential is sent in the clear over plain HTTP. With TLS in front, set `SESSION_COOKIE_SECURE=1`
- **Anyone with UI access controls the proxy.** The blast radius is the container: it mounts no Docker socket and holds no host credentials
- No rate limiting or account lockout on sign-in

## Reference

### Defaults

| Setting         | Value                                                                                        |
| --------------- | -------------------------------------------------------------------------------------------- |
| Proxy port      | `3128`                                                                                       |
| Web UI port     | `3130`                                                                                       |
| Configuration   | `/etc/squid/squid.conf`                                                                      |
| Access log      | `/var/log/squid/access.log`, rotated daily, 5 generations kept                               |
| Cache           | `/var/spool/squid`, 500 MB, 2 MB maximum object size                                         |
| Allowed sources | `10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`, `127.0.0.1/32`                              |
| Allowed ports   | `80` for HTTP, `443` for HTTPS CONNECT                                                       |
| Image           | `us-docker.pkg.dev/litmus-customer-facing/litmus-solutions/forwardproxy`, `linux/amd64` only |

### Environment Variables

| Variable                | Default | Description                                                                                        |
| ----------------------- | ------- | -------------------------------------------------------------------------------------------------- |
| `UI_USERNAME`           | `admin` | Sign-in username                                                                                   |
| `UI_PASSWORD`           | (unset) | Sign-in password. Unset serves every page unauthenticated. Superseded by a password set in the UI. |
| `SESSION_MAX_AGE`       | `43200` | Session lifetime in seconds                                                                        |
| `SESSION_COOKIE_SECURE` | (unset) | Set to `1` when the UI is served over HTTPS                                                        |
| `LOG_LINES`             | `200`   | Log lines shown by default                                                                         |
| `MAX_LOG_LINES`         | `5000`  | Upper bound on the `lines` query parameter                                                         |
| `STATS_LINES`           | `20000` | Log lines analyzed for the Stats page                                                              |

The configuration path, log path, and proxy address have variables too (`SQUID_CONF_PATH`, `SQUID_LOG_PATH`, `SQUID_HOST`, `SQUID_PORT`), but the Config page sets them, persists them, and overrides the variable.

### Volumes

| Mount                  | Holds                                                                 |
| ---------------------- | --------------------------------------------------------------------- |
| `/etc/squid`           | `squid.conf`. The shipped default is restored if the volume is empty. |
| `/var/log/squid`       | `access.log`, what Logs and Stats read                                |
| `/var/spool/squid`     | Disk cache                                                            |
| `/var/lib/squidviewer` | Paths set from the UI, and the hashed password                        |

Anything not on a volume is lost when the container is recreated.
