Skip to content

TLS

Clients may encrypt the connection to the proxy, on the same ports: a connection starting with a TLS handshake is decrypted, and HTTP or SOCKS5 is detected inside. Credentials then don’t travel in the clear. TLS is on while a certificate is active: add one under TLS certificates in the admin UI, the first one is activated right away. Without one, TLS clients get a handshake failure, plain HTTP and SOCKS5 keep working.

Terminal window
curl -x https://username:[email protected]:8080 https://example.com

The certificate must be for the host name clients connect to. One certificate is active at a time and serves all ports, for several names use one with all of them in it. Activating another one, on adding it or later on its page, turns the active one off: new connections get it within the certificate cache TTL (settings), open ones keep theirs, no restart needed. The proxy looks the certificate up once per that TTL, not on every connection: TLS clients can’t load the database before they authenticate.

The admin UI names the one turned off, and if another admin has activated one meanwhile, it refuses and shows the new state instead of turning off a certificate you haven’t seen. The active one can’t be deleted, deactivate it first.

A self-signed certificate works too, the admin UI generates one for given names. Clients trust it only if told to: download the certificate from its page for curl’s --proxy-cacert, or skip the check with --proxy-insecure.

Terminal window
curl -x https://username:[email protected]:8080 --proxy-cacert proxium-1.pem https://example.com

A renewed certificate, e.g. from Let’s Encrypt, goes in with importcert, see Management commands. certbot can run it after every renewal, with the same environment as the proxy:

Terminal window
certbot renew --deploy-hook 'cd /opt/proxium && uv run --env-file .env proxium-manage importcert --no-input \
--cert "$RENEWED_LINEAGE/fullchain.pem" --key "$RENEWED_LINEAGE/privkey.pem"'

Private keys are stored encrypted with ENCRYPTION_KEY, the API never returns them. Generate the key once and give the same one to the proxy, the API and the management commands:

Terminal window
python3 -c "import base64, os; print(base64.urlsafe_b64encode(os.urandom(32)).decode())"

To replace the key without downtime, e.g. after a leak:

  1. Generate a new key. Set it as ENCRYPTION_KEY and the current one as ENCRYPTION_OLD_KEYS for the proxy, the API and the management commands, then restart them. Both keys now decrypt, new secrets get the new one.

  2. Re-encrypt the stored secrets with the new key. Stopped halfway, it’s just run again:

    Terminal window
    uv run --env-file .env proxium-manage rotateencryptionkey
  3. Remove ENCRYPTION_OLD_KEYS everywhere and restart again.

The proxy needs a writable temporary directory: Python’s ssl loads a key only from a file, not from memory (python/cpython#60691), so the decrypted key goes to a file readable by the proxy’s user alone and is removed right after loading. In a container with a read-only root, point TMPDIR to a tmpfs, e.g. an emptyDir with medium: Memory in Kubernetes: the key then never reaches a disk. Without it, TLS clients are refused with “Can’t write the certificate to a temporary file” in the log.

SOCKS5 inside TLS works too, but few clients support it, curl doesn’t. HTTP/2 to the proxy isn’t supported, clients fall back to HTTP/1.1.