New deployments/squid/: an explicit forward proxy with SSL-bump TLS interception (local CA, generated on first deploy) and hostname-targeted static-content caching. Unlike the other stacks it is a forward proxy, not a Caddy/Let's-Encrypt inbound site. - Self-built minimal Alpine image (apk squid ships ssl-bump); entrypoint renders squid.conf and generates the cache policy from the domain lists. - Wildcard hostname caching (cache-domains.txt leading-dot + optional cache-domains.regex); boost vs strict-allowlist toggle (CACHE_ONLY_LISTED). - Storage gate never caches HTML or dynamic content; query strings exempt on boosted domains so versioned static assets still cache. - splice-domains.txt passthrough for pinned/banking domains. - Deny-by-default http_access (TRUSTED_CIDR) + BIND_ADDR pinning; CA key 0600 on host, never embedded, git-ignored. - Wired into automations.sh, README, .gitignore; cloud-init for fresh VMs.
6.1 KiB
squid — SSL-bump caching forward proxy
A Squid forward proxy that caches static content to cut bandwidth across a fleet and intercepts TLS (SSL-bump) with a locally-generated CA so HTTPS can be cached and inspected.
This is the one deployment that breaks the repo's Caddy/Let's-Encrypt
convention on purpose: it is not an inbound web service, has no public
hostname, and uses no ACME cert. Clients point HTTP(S)_PROXY at it and trust
its CA.
⚠️ TLS interception — read first. SSL-bump decrypts your clients' HTTPS. The CA private key it generates can impersonate any site to any client that trusts it. Only run this on networks and devices you own and are authorized to inspect. Keep
ssl/squid-ca-key.pemsecret. Cert-pinned and HSTS sites (banking, app stores) will break unless you splice them (passthrough, see below).
What it does
- Explicit forward proxy on port
3128. Clients sethttp_proxy/https_proxy(transparent/intercepting mode is future work — see end). - SSL-bump: peek at the TLS SNI → splice (passthrough, no decryption)
the domains in
splice-domains.txt→ bump (decrypt) everything else, minting per-host leaf certs on the fly from the local CA. - Hostname-targeted caching with wildcards, and a storage gate that never caches HTML or dynamic content.
Deploy
Via the launcher (from a clone or the one-liner): pick deploy: squid, then
answer the prompts (trusted CIDR, etc.).
Standalone (self-contained deploy.sh, scp'd to a host):
TRUSTED_CIDR=100.64.0.0/10 BIND_ADDR=100.64.0.1 SKIP_PROMPTS=1 bash deploy.sh
Fresh VM: paste cloud-init.yml as user-data (it hardens
SSH first, then deploys).
The deploy is idempotent. On first run it builds the local image, generates the
CA into ssl/ (never overwritten), seeds .env, registers the port with the
host firewall if present, and brings the stack up.
Point clients at it
export http_proxy=http://<host>:3128
export https_proxy=http://<host>:3128
Per-tool: apt → Acquire::http(s)::Proxy "http://<host>:3128";; dnf →
proxy=http://<host>:3128 in /etc/dnf/dnf.conf; apk → http_proxy env or
--proxy.
Trust the CA (so bumped HTTPS validates) — distribute ssl/squid-ca-cert.pem:
| Client | Install |
|---|---|
| Debian/Ubuntu | cp squid-ca-cert.pem /usr/local/share/ca-certificates/squid-ca.crt && update-ca-certificates |
| Alpine | cp squid-ca-cert.pem /usr/local/share/ca-certificates/squid-ca.crt && update-ca-certificates |
| Alma/RHEL | cp squid-ca-cert.pem /etc/pki/ca-trust/source/anchors/squid-ca.pem && update-ca-trust |
Browsers, Java, and some language runtimes keep their own trust stores — import there too if needed.
Caching model
Two knobs and three lists, all in the stack dir; they are bind-mounted, so edit
then docker compose restart.
cache-domains.txt— hostnames to cache hard (long TTL, force-cache pastCache-Control: private/no-store). A leading dot is a subdomain wildcard:.ubuntu.commatchesubuntu.comand every subdomain.cache-domains.regex— optionaldstdom_regexpatterns for wildcards inside a label (e.g.^mirror[0-9]+\.example\.com$). Comments-only = disabled.CACHE_ONLY_LISTED(.env):0(default, boost): cache everything per normal HTTP rules, and force-cache the listed domains aggressively.1(strict allowlist): store only the listed domains; pass the rest through uncached.
Never cached (storage gate, applies even to boosted domains): HTML (by
.html extension and text/html content-type) and dynamic content (script
endpoints + query strings). Query strings are exempt on boosted domains, so
versioned static assets like app.js?v=123 still cache there.
splice ⇄ cache are mutually exclusive
A spliced domain is passed through encrypted — there is nothing to cache or
inspect. Do not list the same domain in both splice-domains.txt and
cache-domains.txt; splice wins.
Security posture
- Access control is Squid's
http_access(TRUSTED_CIDR), deny-by-default. This matters because a published Docker port bypasses the hostINPUTfirewall — so also pinBIND_ADDRto a trusted interface (e.g. your Tailscale IP). The/etc/firewall/ports.d/squid.ruleentry is belt-and-braces. - CA key lives at
ssl/squid-ca-key.pem, mode0600root, mounted read-only; the container stages a squid-readable copy into/run(tmpfs) at start. The key never enters the embedded archive and is git-ignored. - Upstream certs are validated (
sslproxy_cert_error deny all) — the proxy won't silently launder a broken origin certificate to clients.
Caching caveats (be realistic)
Even with bump, much of the web is Cache-Control: private/no-store, dynamic,
or personalized. The real wins are distro packages (apk/apt/dnf),
container layers, OS updates, and large static assets across many hosts
— which is what the default cache-domains.txt targets. It is not a blanket
"cache the whole internet."
Files
| File | Purpose |
|---|---|
Dockerfile |
Minimal Alpine image (apk add squid — ssl-bump compiled in). |
entrypoint.sh |
Renders squid.conf, generates the cache policy from the lists, stages the CA, inits the cert DB/cache, starts squid. |
squid.conf.tmpl |
Static config (ports, bump policy, access control, cache sizing). |
cache-domains.txt / .regex |
Wildcard hostnames to cache hard. |
splice-domains.txt |
Domains to pass through without decrypting. |
docker-compose.yml / .env.example |
Stack definition + tunables. |
deploy.sh / build.sh |
Self-contained installer + archive embedder. |
cloud-init.yml |
Fresh-VM bootstrap (harden SSH, then deploy). |
Future work
- Transparent / intercepting mode — add
intercept/tproxyports and an iptablesREDIRECTrecipe for when the box is the gateway (clients need no proxy config). Out of scope for v1. - Upstream
cache_peerchaining; access-log shipping / dashboard.