Proxies and routing
Squid
ACL evaluation order, caching behaviour, authentication, TLS handling and log analysis for a forward proxy.
Cheatsheet #
| Task | Command |
|---|---|
| Check config before reload | squid -k parse |
| Reload without dropping sessions | squid -k reconfigure |
| Rotate logs | squid -k rotate |
| Runtime statistics | squidclient mgr:info |
| Current connections | squidclient mgr:active_requests |
| Cache hit ratio | squidclient mgr:info | grep -i 'hit ratio' |
| Tail decisions | tail -f /var/log/squid/access.log |
| Test a URL through the proxy | curl -x localhost:3128 -sI https://example.com |
| Why was it denied | grep TCP_DENIED /var/log/squid/access.log | tail |
| Initialise cache directories | squid -z |
| Purge one object | squidclient -m PURGE http://example.com/file |
| Which ACL matched | debug_options ALL,1 33,2 28,9 then read cache.log |
How a request is decided #
Squid walks http_access rules top to bottom and stops at the first match; the action of that rule wins. An allow after a matching deny never runs. Each rule is a set of ACL names ANDed together, and multiple lines are ORed.
acl localnet src 10.0.0.0/8
acl SSL_ports port 443
acl Safe_ports port 80 443 21 70 210 1025-65535
acl CONNECT method CONNECT
http_access deny !Safe_ports
http_access deny CONNECT !SSL_ports
http_access allow localnet
http_access deny all # keep this last; the implicit default is the opposite of the last ruleThe implicit final rule is the negation of the last explicit one, which is a reliable way to be surprised — always end with http_access deny all.
squid -k parse # syntax and ACL sanity
grep -E 'TCP_DENIED|TAG_NONE' /var/log/squid/access.log | tail -20
curl -x localhost:3128 -sI https://example.com # end-to-end checkACL types #
| Type | Matches | Example |
|---|---|---|
src / dst | Client / server address | acl office src 10.1.0.0/16 |
dstdomain | Destination domain | acl allowed dstdomain .example.com |
dstdom_regex | Domain regex | acl bad dstdom_regex -i ads?\. |
url_regex | Full URL regex | acl media url_regex -i \.(mp4|iso)$ |
port | Destination port | acl SSL_ports port 443 |
method | HTTP method | acl CONNECT method CONNECT |
time | Day and hour | acl work time MTWHF 08:00-18:00 |
proxy_auth | Authenticated user | acl users proxy_auth REQUIRED |
maxconn | Concurrent connections per client | acl heavy maxconn 20 |
req_mime_type | Request content type | acl upload req_mime_type -i multipart/form-data |
A leading dot in dstdomain matches the domain and all subdomains; without it the match is exact. Keep long lists in files: acl allowed dstdomain "/etc/squid/allowed.txt".
Minimal working configuration #
http_port 3128
visible_hostname proxy.example.internal
acl localnet src 10.0.0.0/8 172.16.0.0/12 192.168.0.0/16
acl SSL_ports port 443
acl Safe_ports port 80 443
acl CONNECT method CONNECT
http_access deny !Safe_ports
http_access deny CONNECT !SSL_ports
http_access allow localhost manager
http_access deny manager
http_access allow localnet
http_access deny all
cache_dir ufs /var/spool/squid 10000 16 256
maximum_object_size 512 MB
cache_mem 512 MB
coredump_dir /var/spool/squid
access_log daemon:/var/log/squid/access.log squid
logfile_rotate 7
forwarded_for delete # do not leak internal client addresses upstream
via off
dns_v4_first onsquid -z # create cache directories, once
systemctl enable --now squid
squid -k reconfigure # after every changeCaching #
Squid obeys origin cache headers. Cache-Control: no-store or private means nothing is cached; missing validators mean revalidation on every request. refresh_pattern only controls heuristics for responses without explicit freshness.
refresh_pattern -i \.(deb|rpm|tgz|whl)$ 10080 90% 43200 override-expire
refresh_pattern ^ftp: 1440 20% 10080
refresh_pattern . 0 20% 4320| Log tag | Meaning |
|---|---|
TCP_HIT | Served from cache |
TCP_MEM_HIT | Served from memory cache |
TCP_REFRESH_UNMODIFIED | Revalidated, origin said unchanged |
TCP_MISS | Fetched from origin |
TCP_DENIED | Blocked by http_access |
TCP_TUNNEL | CONNECT tunnel, contents never cacheable |
squidclient mgr:info | grep -iE 'hit ratio|objects|Storage'
squidclient mgr:storedir
squidclient -m PURGE http://example.com/path/fileHTTPS traffic through CONNECT is an opaque tunnel: nothing is cached and only the hostname is visible. A caching proxy in front of package mirrors needs those mirrors to be reachable over plain HTTP, or SSL bumping, which is a separate decision.
Authentication #
auth_param basic program /usr/lib/squid/basic_ncsa_auth /etc/squid/passwd
auth_param basic children 20
auth_param basic realm Corporate proxy
auth_param basic credentialsttl 2 hours
acl authenticated proxy_auth REQUIRED
http_access allow authenticatedhtpasswd -c /etc/squid/passwd alice # basic auth fileFor directory-backed authentication use basic_ldap_auth or Kerberos (negotiate_kerberos_auth), which avoids passwords entirely on domain-joined clients. Basic authentication sends credentials on every request — acceptable only inside a trusted network or over TLS to the proxy.
Transparent and reverse modes #
# Intercept mode: traffic redirected by the router or iptables
http_port 3129 intercept
# Reverse proxy (accelerator)
http_port 80 accel defaultsite=app.example.com
cache_peer 10.0.0.5 parent 8080 0 no-query originserver name=app
acl our_sites dstdomain app.example.com
http_access allow our_sites
cache_peer_access app allow our_sitesIntercepting HTTPS requires either a client-trusted CA (SSL bump) or leaving CONNECT tunnels intact. Bumping decrypts user traffic — get written authorisation before deploying it, and exclude banking and health categories explicitly.
Upstream proxies #
cache_peer upstream.example.com parent 3128 0 no-query default
never_direct allow all # force everything through the parent
acl internal dstdomain .internal.example.com
always_direct allow internal # except thesenever_direct plus always_direct for exceptions is the standard shape in a restricted network; getting the order wrong silently leaks direct connections.
Logs and monitoring #
logformat combined %>a %[ui %[un [%tl] "%rm %ru HTTP/%rv" %>Hs %<st "%{Referer}>h" "%{User-Agent}>h" %Ss:%Sh
access_log daemon:/var/log/squid/access.log combinedtail -f /var/log/squid/access.log
grep -c TCP_DENIED /var/log/squid/access.log
awk '{print $4}' /var/log/squid/access.log | sort | uniq -c | sort -rn | head # result codes
squidclient mgr:info
squidclient mgr:5min | grep -E 'client_http|server_http'Export to Prometheus with squid-exporter, alerting on denied ratio, file descriptor use and cache disk fullness. Running out of file descriptors presents as random connection failures under load.
Troubleshooting #
| Symptom | Check |
|---|---|
| Everything denied | Rule order; an early deny matched. Read TCP_DENIED lines |
| Works by IP, not by name | Squid’s own DNS: dns_nameservers, dns_v4_first |
| HTTPS sites fail, HTTP works | CONNECT denied by Safe_ports/SSL_ports |
| Slow first byte | Upstream DNS or parent proxy latency, not cache |
Too many open files | Raise LimitNOFILE in the systemd unit and max_filedescriptors |
| Cache never hits | Origin sends no-store/private, or traffic is all HTTPS tunnels |
| Reconfigure had no effect | squid -k parse failed silently in the unit; check journalctl -u squid |
Oneliners #
# Top destinations
awk '{print $7}' /var/log/squid/access.log | awk -F/ '{print $3}' | sort | uniq -c | sort -rn | head
# Top clients by request count
awk '{print $3}' /var/log/squid/access.log | sort | uniq -c | sort -rn | head
# Bytes served per client
awk '{b[$3]+=$5} END {for (c in b) printf "%12d %s\n", b[c], c}' /var/log/squid/access.log | sort -rn | head
# Denied requests with the URL
awk '$4 ~ /DENIED/ {print $3, $7}' /var/log/squid/access.log | sort | uniq -c | sort -rn | head
# Hit ratio from the log itself
awk '{if ($4 ~ /HIT/) h++; t++} END {printf "%.1f%% of %d\n", 100*h/t, t}' /var/log/squid/access.log
# Requests per minute
awk '{print strftime("%H:%M", $1)}' /var/log/squid/access.log | uniq -c | tail -20
# Verify a client can reach a site through the proxy
curl -x proxy.example.internal:3128 -o /dev/null -sw '%{http_code} %{time_total}\n' https://example.com
# Check which ACL blocked a URL
squidclient -h localhost -p 3128 mgr:config | grep -A2 http_access
# Confirm the parent proxy is being used
grep -c 'FIRSTUP_PARENT' /var/log/squid/access.log
# Watch cache disk usage
squidclient mgr:storedir | grep -E 'Maximum|Current'
# Reload safely in a pipeline
squid -k parse && squid -k reconfigure