Squid SSL-Bump

What CONNECT actually does, the four ssl_bump modes and the decision flow between them, how to generate and trust the Squid CA, and a minimal working squid.conf — with a large privacy/legal warning up front.

SSL-Bump is a sanctioned man-in-the-middle. It decrypts, inspects, then re-encrypts every HTTPS flow you don't explicitly splice. In many jurisdictions you must tell users, allow opt-out, and keep a hard no-bump list for banking, healthcare and government. If any of the traffic crosses a border, have your legal team sign off on the deployment before you flip ssl_bump from peek to bump.
Think in phases. SSL-Bump decisions happen at two points: at CONNECT (when all you have is the SNI) and after peek (when you have the server's certificate). Decide at the earliest phase whether to splice or bump. Any site the business cannot afford to break goes in a no_bump list evaluated at the first phase.

What CONNECT does

When a browser configured to use a proxy requests an https:// URL, it does not perform the TLS handshake directly. It sends the proxy an HTTP CONNECT verb:

CONNECT www.example.com:443 HTTP/1.1
Host: www.example.com:443
User-Agent: Mozilla/5.0 ...

A "dumb" proxy (no SSL-Bump) replies 200 Connection established and blindly forwards the subsequent TLS bytes between client and server. It has no visibility into URL, method, or body.

SSL-Bump intercepts that CONNECT. Squid decides — based on its ACLs and the SNI field in the client hello — whether to:

ssl_bump modes

ModeWhat Squid doesTypical use
splice Transparent passthrough — no decrypt. Squid sees only SNI. Default for banks, government, personal email. The safe choice.
peek Read the client hello (get the SNI), optionally read the server hello (get cert CN/SAN). Does not decrypt the application traffic unless a later step bumps. Diagnostic step before a bump/splice decision.
stare Like peek but takes control of the TLS handshake with the server. Cannot be followed by splice — you must then bump or terminate. Rarely needed. Use if you need full server cert validation choices upstream.
bump Full MITM: Squid terminates TLS from the client with a cert it signed, and opens its own TLS to the server. DLP, URL filtering by path, content scanning.
terminate Close the connection. Deny policy (e.g. "no HTTPS to category X").
Peek-then-Splice vs Peek-then-Bump is the canonical design. Peek first to learn the SNI, then splice or bump based on destination. Starting with stare forecloses the option to splice, which is rarely what you want.

Decision flow

There are up to four steps in a single SSL-Bump handshake. You can attach ACLs to each with ssl_bump <action> [acl] [at_step N]:

  1. Step 1 — Client hello seen. You know the SNI, client IP, user (via authenticated proxy), TCP outbound interface. Best chance to splice for no_bump domains.
  2. Step 2 — Server hello seen. You know the origin's cert chain, SAN, issuer. Useful for trust-level-based policy ("don't bump if cert is EV").
  3. Step 3 — Client certificate processed. Rare in practice.
  4. Step 4 — Server certificate processed. Last chance to change your mind before the app-data phase.
# Conceptual flow in squid.conf
acl step1 at_step SslBump1
acl step2 at_step SslBump2
acl step3 at_step SslBump3

acl no_bump ssl::server_name_regex "/etc/squid/no_bump.regex"
acl bump_all src 10.0.0.0/8

ssl_bump peek step1
ssl_bump splice step2 no_bump
ssl_bump bump step2 bump_all
ssl_bump terminate step2 all

Read it aloud: "At step 1, peek. At step 2, if this SNI matches no_bump, splice. Otherwise, if the client is on our network, bump. Otherwise terminate."

Generating the Squid CA

mkdir -p /etc/squid/ssl
cd /etc/squid/ssl

# 10-year CA — acceptable internally; externally signed CAs are not allowed to
# issue unconstrained intermediates, so you must self-sign.
openssl req -new -newkey rsa:4096 -days 3650 -nodes -x509 \
  -keyout squidCA.key -out squidCA.crt \
  -subj "/C=US/ST=NY/O=Example Co/OU=ITSec/CN=Example Squid SSL-Bump CA"

# Combine for Squid's cert generator:
cat squidCA.crt squidCA.key > squidCA.pem
chown -R squid:squid /etc/squid/ssl
chmod 600 squidCA.key squidCA.pem

# Prime the ssl_db directory for sslcrtd_program:
/usr/lib64/squid/security_file_certgen -c -s /var/lib/squid/ssl_db -M 16MB
chown -R squid:squid /var/lib/squid/ssl_db

Squid's on-the-fly cert generator (security_file_certgen on modern Squid, formerly ssl_crtd) caches the certs it synthesizes for each origin so it doesn't resign from scratch on every CONNECT. 16MB is enough for thousands of cached origin certs.

Distributing trust to clients

Every client that will traverse the proxy must trust squidCA.crt. Otherwise every bumped connection is a certificate-error nightmare.

PlatformHow
Windows (domain)GPO → Computer Configuration → Windows Settings → Security Settings → Public Key Policies → Trusted Root Certification Authorities → Import.
Windows (standalone)certutil -addstore -enterprise -f Root squidCA.crt
macOSsecurity add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain squidCA.crt
RHEL-family Linuxcp squidCA.crt /etc/pki/ca-trust/source/anchors/ && update-ca-trust
Debian/Ubuntucp squidCA.crt /usr/local/share/ca-certificates/squidCA.crt && update-ca-certificates
Firefox (separate trust store)Either security.enterprise_roots.enabled = true in about:config (Firefox reads the OS store), or import per-profile.
Never copy squidCA.key to endpoints. The private key stays on the Squid host only. If it ever leaks, every TLS session ever bumped with it is retroactively readable by whoever has the key — generate a new CA, re-deploy, revoke the old one.

Never-bump lists

Regulatory and user-trust reasons to always splice certain destinations:

# /etc/squid/no_bump.regex — regex match against SNI
\.bank\.(com|co\.uk)$
^.*\.chase\.com$
^.*\.irs\.gov$
^.*\.login\.gov$
^.*\.nhs\.uk$
^.*\.icloud\.com$
^.*\.apple\.com$
^.*\.microsoft\.com$
^.*\.windowsupdate\.com$
^.*\.mozilla\.org$

Regex match is expensive per-connection — if the list grows past a couple hundred entries, switch to domain match with acl no_bump ssl::server_name "/etc/squid/no_bump.dstdomain" and a literal-FQDN list.

Performance impact

Bumping doubles TLS handshake CPU and adds a per-origin cert-generation step on the first hit. Rough rules of thumb on modern hardware:

Sizing: a single Squid host with 4 cores and 8 GB RAM handles ~500 concurrent authenticated users with full bump if you keep the cert DB warm and block HTTP/3 (QUIC) at the firewall so traffic falls back to TCP.

Minimal working squid.conf

http_port 3128 ssl-bump \
  cert=/etc/squid/ssl/squidCA.pem \
  generate-host-certificates=on dynamic_cert_mem_cache_size=16MB \
  tls-dh=prime256v1:/etc/squid/ssl/dhparam.pem

sslcrtd_program /usr/lib64/squid/security_file_certgen -s /var/lib/squid/ssl_db -M 16MB
sslcrtd_children 8 startup=1 idle=1

acl localnet src 10.0.0.0/8
acl Safe_ports port 80 443
acl CONNECT method CONNECT

acl step1 at_step SslBump1
acl step2 at_step SslBump2

acl no_bump ssl::server_name_regex "/etc/squid/no_bump.regex"
acl bump_users src 10.0.0.0/8

# Peek first to get SNI; then splice or bump.
ssl_bump peek     step1
ssl_bump splice   step2 no_bump
ssl_bump bump     step2 bump_users
ssl_bump terminate all

http_access deny !Safe_ports
http_access deny CONNECT !Safe_ports
http_access allow localnet
http_access allow localhost
http_access deny all

# Logging — drop from access.log the SNI when we spliced, for privacy.
logformat squidmime %ts.%03tu %6tr %>a %Ss/%03>Hs %<st %rm %ru %[un %Sh/%<a %mt
access_log /var/log/squid/access.log squidmime

cache_mem 512 MB
maximum_object_size_in_memory 64 KB
coredump_dir /var/spool/squid

Generate the DH params once:

openssl dhparam -out /etc/squid/ssl/dhparam.pem 2048
chown squid:squid /etc/squid/ssl/dhparam.pem

Start:

squid -k parse        # syntax check
systemctl enable --now squid
journalctl -u squid -f

Testing with curl

Without Squid's CA in the local trust store, a bumped fetch fails cert verify — that's the smoke test:

# Smoke test: should fail verify because curl doesn't trust squidCA yet
curl -v -x http://proxy.example.com:3128 https://httpbin.org/get
# -> curl: (60) SSL certificate problem: unable to get local issuer certificate

# With the CA explicitly trusted:
curl -v --cacert /etc/squid/ssl/squidCA.crt \
  -x http://proxy.example.com:3128 https://httpbin.org/get
# -> 200, body returned

# Confirm splice path — a no_bump destination returns the real origin cert:
curl -v --cacert /etc/ssl/certs/ca-bundle.crt \
  -x http://proxy.example.com:3128 https://www.chase.com/ -o /dev/null
# Squid access.log should show "splice" in the result code column.

Troubleshooting

Symptom / logRoot causeFix
SECURITY ALERT: Host header forgery detected Client connects to an IP that doesn't match the SNI DNS resolution Squid does for itself. Ensure the Squid host has working DNS. Add host_verify_strict off only as a last resort — it weakens a useful check.
All bumped sites show browser "Your connection is not private" Squid CA not trusted on the client. Deploy squidCA.crt; see Distributing trust.
Banking sites break with cert-pinning error Bump is applied to a site that must stay end-to-end. Add to no_bump.regex, reload Squid. Verify with access.log.
sslcrtd_program crashes / zombie children SSL DB directory permissions wrong, or disk full. chown -R squid:squid /var/lib/squid/ssl_db; security_file_certgen -c -s ... to re-init.
Windows Update / macOS Software Update fails through the proxy Bumped. Update endpoints validate their own chain, not yours. Always splice: add *.windowsupdate.com, *.swscan.apple.com, *.mzstatic.com to no-bump.
Mobile apps refuse to work (Slack, Teams, Zoom) Certificate pinning or mutual TLS. Splice those domains. The vendor publishes their domain lists.
TLS handshake latency visibly high on first hit to a site Cert DB cold, expected for first visit. Raise dynamic_cert_mem_cache_size and -M on security_file_certgen. Pre-warm with a daily script hitting the top 500 destinations.
HTTP/3 (QUIC) traffic appears to "escape" the proxy QUIC is UDP/443; Squid is TCP only. Block UDP/443 outbound at the firewall to force fallback to TCP/443 via the proxy.
Reusable. Pair with Nginx for a reverse-proxied web tier and SELinux for the system hardening around Squid's key material. The no_bump.regex file is the artifact you revisit most — treat it as source-controlled policy, not a live-edit config.