Squid 正向代理缓存实战:个人站统一出口、静态资源缓存策略与 ACL 访问控制完整指南

为什么个人站长也需要一个正向代理

大多数站长对 Nginx 的印象停留在「反向代理」:外面的人来访问我的站点,Nginx 把请求分发给后端。可反向代理解决的是「别人怎么进来」,而正向代理解决的是「我的服务器怎么出去」。这两件事经常被混为一谈,直到你的服务器需要抓取外部数据、需要统一下载源、需要给内网机器共享一条出口链路,才发现光有 Nginx 是不够的。

举个最常见的场景:你在 VPS 上跑了一个定时任务,每天要去几个外部接口拉数据,或者要用 wget 下载一批资源。时间一长,你会发现几件事很烦——目标站点看到的是你服务器的真实 IP,一旦被限流整台机器就都完了;重复下载的静态资源没有本地缓存,每天都在白白消耗流量;如果服务器在内网、没有独立公网出口,那更是抓也抓不了、下也下不动。

正向代理(Forward Proxy)正好对症下药。它的角色是「替客户端去访问外网」,客户端把请求交给代理,代理去取回结果再返回。Squid 是这个领域里最老牌、最稳定的开源实现之一,配置不复杂,一台 1 核 1G 的小机器就能扛起来,非常适合个人站长。

Squid 安装与最小可用配置

Squid 在主流发行版里都有官方包,Debian/Ubuntu 下直接装即可:

apt update
apt install -y squid
squid -v        # 确认版本,本书以 5.x 为例
systemctl status squid

发行版自带的默认配置会监听 3128 端口,但默认只允许本机访问(`http_access allow localhost`),并且会打开一堆不必要的功能。我们从一个干净的最小配置重新写起,配置文件位于 `/etc/squid/squid.conf`。修改前先备份:

cp /etc/squid/squid.conf /etc/squid/squid.conf.orig

一个「只给本机用、带磁盘缓存」的最小配置如下:

# 监听端口,只绑定内网/本机,绝不裸奔到公网
http_port 3128

# 缓存目录:10000 个一级目录、16 个二级目录
# 单位 MB,这里给 10GB 缓存空间
cache_dir ufs /var/spool/squid 10240 16 256

# 缓存内存对象上限
cache_mem 256 MB

# 只允许本机与内网网段使用代理
acl localnet src 127.0.0.1/32
acl localnet src 10.0.0.0/8
acl localnet src 172.16.0.0/12
acl localnet src 192.168.0.0/16
http_access allow localnet
http_access deny all

# DNS 解析器(用本机或公共 DNS)
dns_nameservers 223.5.5.5 8.8.8.8

# 缓存友好:允许缓存带查询字符串的 GET
refresh_pattern .  0  20%  4320

改完以后初始化缓存目录并重启:

squid -z            # 创建缓存目录结构,只需执行一次
systemctl restart squid
systemctl enable squid

squid -z 这一步千万不能省,否则 Squid 会因为缓存目录不存在而拒绝启动,日志里会提示 Failed to make swap directory。

把 Squid 变成一个真正的缓存加速器

光是能转发请求还不算「加速」,我们要让 Squid 真的把重复资源缓存下来。核心是 refresh_pattern 指令,它决定了什么样的响应可以被判定为「新鲜」,从而命中缓存直接返回。

refresh_pattern 的格式是:refresh_pattern <正则> <最小新鲜时间> <百分比> <最大新鲜时间>。三个数字的含义是这样的:

  • 最小新鲜时间(min):资源进入缓存后,在这段时间内无论如何都认为是新鲜的,直接命中。
  • 百分比(percent):结合响应里的 Last-Modified 和 Date 计算年龄,超过这个比例就认为过期。
  • 最大新鲜时间(max):超过这个时间无论如何都要重新验证。

针对常见的静态资源,可以写一组更精细的规则放在默认规则之前(Squid 从上往下匹配,第一条命中即生效):

refresh_pattern -i \.(gif|png|jpg|jpeg|ico|webp|avif)$  1440 90% 10080
refresh_pattern -i \.(css|js)$                          1440 90% 10080
refresh_pattern -i \.(mp4|webm|mp3|woff2?)$             10080 90% 43200
refresh_pattern -i (/cgi-bin/|\?)  0  0%  0
refresh_pattern .                   0  20% 4320

这组规则的意思是:图片、CSS、JS 这类「基本不变」的资源,进缓存后至少 1440 分钟(一天)内直接命中,最多保留 7 天;字体、音视频这种大文件保留更久;而带查询字符串的动态请求一律不缓存,避免把带用户态的页面缓存成公共副本。

用日志判断缓存到底有没有生效

配好之后最忌讳的就是「我配了,但它到底有没有起作用」。Squid 的访问日志里,每一行的第三段就是命中状态,用 awk 统计一下命中率就一目了然:

# 统计各类命中状态数量
awk '{print $4}' /var/log/squid/access.log | sort | uniq -c | sort -rn

常见的状态码含义如下:

  • TCP_HIT:直接命中内存或磁盘缓存,请求没有出网,这是最好的结果。
  • TCP_MISS:缓存里没有,回源取了一次并写入缓存。
  • TCP_REFRESH_MODIFIED:缓存里有,但资源已更新,重新取回。
  • TCP_DENIED:被 ACL 拒绝,通常是 IP 不在允许范围,或目标端口被禁止。
  • TCP_TUNNEL:HTTPS 的 CONNECT 隧道,属于透传,不缓存。

如果你发现 TCP_HIT 占比长期是零,八成是这两个原因:一是目标资源响应头里带了 Cache-Control: no-store 或 private,Squid 会老老实实不缓存;二是 refresh_pattern 被写在了默认规则之后,永远轮不到它匹配。把规则顺序调对,命中率立刻就上来了。

HTTPS 请求怎么走代理

现代站点几乎全是 HTTPS,而 HTTPS 请求走正向代理的方式和 HTTP 完全不同:客户端会先发一个 CONNECT 请求,让 Squid 帮忙建立一条 TCP 隧道。隧道里跑的是端到端加密流量,代理看不见内容,自然也就无法缓存。

这意味着两件事。第一,Squid 必须显式允许 CONNECT 方法,否则所有 HTTPS 请求都会被拒绝:

acl SSL_ports port 443 563
acl CONNECT method CONNECT
http_access allow localnet CONNECT SSL_ports
http_access deny CONNECT !SSL_ports

第二,你要对「HTTPS 无法缓存」这件事有正确的预期。想要真的缓存 HTTPS 内容,唯一可行的办法是让 Squid 做 SSL 中间人(SSL Bump),也就是把证书解密后再缓存。但这需要给每台客户端安装自签名根证书,对个人站长来说运维成本高、且会引入信任风险,除非你有非常明确的内网需求,否则不建议开启。常见的务实做法是:把 HTTPS 流量当作「透传加速」——虽然不能缓存,但走代理能让出口 IP 统一、便于做访问控制和日志审计,这已经能解决很多问题。

把 Squid 当统一出口:ACL 与访问控制

Squid 的 ACL 是它最强大的部分,用好了能替代掉不少繁琐的 iptables 规则。它的基本思路是「先定义集合,再决定放行或拒绝」。除了前面用的源地址 ACL,常用的还有按域名、按时间、按端口做限制。

比如你想让服务器的下载流量只允许访问白名单域名,防止定时任务被利用去抓取外部不明地址:

acl allowed_domains dstdomain .pypi.org .github.com .debian.org
acl localnet src 10.0.0.0/8
http_access allow localnet allowed_domains
http_access deny localnet

再比如你想限制代理只在夜间开放,白天不占用带宽:

acl night time 22:00-06:00
http_access allow localnet night
http_access deny localnet

而按目标端口做限制,可以防止有人拿你的代理去连各种奇怪的端口:

acl Safe_ports port 80          # http
acl Safe_ports port 443         # https
acl Safe_ports port 21          # ftp
acl Safe_ports port 1025-65535  # unregistered ports
http_access deny !Safe_ports

这一条 deny !Safe_ports 是所有 Squid 配置里最值得抄的兜底规则。很多代理被拿去做垃圾邮件中继、被扫描器利用,就是因为忘了限制目标端口。

给客户端接上代理

配置好服务端,还要让客户端知道走代理。Linux 命令行下最直接的方式是设置环境变量:

export http_proxy=http://127.0.0.1:3128
export https_proxy=http://127.0.0.1:3128
export no_proxy=localhost,127.0.0.1,10.0.0.0/8

要注意大小写问题:很多老程序只认小写的 http_proxy,而有些只认大写,稳妥的做法是两个都设。curl 与 wget 都遵循这套约定,apt 则需要单独在 /etc/apt/apt.conf.d/ 下写代理配置:

echo 'Acquire::http::Proxy "http://127.0.0.1:3128";' > /etc/apt/apt.conf.d/99proxy

如果是桌面环境或者要给整个内网共享代理,可以在路由器上开 DHCP 的 WPAD,或者直接手动在浏览器里填代理地址。需要注意的是,Squid 默认监听所有网卡,如果这台机器有公网 IP,务必把 http_port 改成只监听内网地址,例如 http_port 10.0.0.1:3128,再配合 http_access deny all 兜底,避免变成开放代理被人白嫖。

几个容易踩的坑

坑一:忘记 squid -z。这是新手第一大坑,表现为服务起不来,日志报缓存目录创建失败。每次改了 cache_dir 都要重新执行一次。

坑二:缓存目录权限。Squid 以 proxy 用户运行,如果 /var/spool/squid 的属主被改错了,会报权限拒绝。正确做法是 chown -R proxy:proxy /var/spool/squid。

坑三:把 Squid 暴露到公网。这是最严重的安全问题。一个开放的正向代理会被滥用去发垃圾邮件、做扫描、甚至被拿来做攻击跳板,最后挨罚的是你。永远记住两条:只绑定内网地址,http_access deny all 兜底。

坑四:磁盘被缓存撑爆。cache_dir 的大小是硬上限,但要留意 Squid 在达到上限时才做淘汰,如果缓存目录和系统盘同一个分区,留足余量。更稳的做法是把缓存目录挂到单独的数据盘,并用 cache_swap_low 90、cache_swap_high 95 控制淘汰水位。

小结

正向代理是被很多站长忽略的一块拼图。它不改变你的网站架构,却能在「服务器怎么出去」这件事上带来实打实的好处:统一出口 IP、降低重复下载、给内网机器共享链路、为下载行为建一道白名单闸门。Squid 的价值不在于配置多花哨,而在于它稳定、开销低、规则表达力强。把最小配置跑通,再按需加缓存规则和 ACL,一台小机器就能成为一个可靠的内网出口网关。

最后提醒一句:任何代理服务的第一原则都是「不暴露」。先把访问来源收紧,再谈性能优化;顺序反了,做出来的就不是加速器,而是一个敞开的门。

Last modification:October 1st, 2026 at 01:30 pm

Leave a Comment