架构
Client (HTTPS)
↓
Nginx (端口80,跳转到443的https;端口443转发到varnish)← 负责 SSL 终止(TLS termination)
↓ (HTTP)
Varnish (端口 6081,缓存层)
↓ (HTTP)
Nginx / Apache (端口 8080,WordPress)
Nginx配置
# http跳转到https
server {
listen 80;
server_name www.dian.com;
return 301 https://$host$request_uri;
}
# 将https流量转发到Varnish
server {
listen 443 ssl http2;
server_name www.dian.com;
ssl_certificate ssl/dian.crt;
ssl_certificate_key ssl/dian.key;
# 推荐 TLS 设置
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE:ECDH:AES256:!NULL:!aNULL:!MD5:!DSS';
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:50m;
ssl_session_timeout 1d;
# 转发到 Varnish
location / {
proxy_pass http://127.0.0.1:6081; # Varnish
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto https; # 关键:让 Varnish 知道这是 HTTPS
}
}
# 从Varnish传入的流量,真正对网站进行处理(正常的WordPress配置)
server {
listen 8080;
server_name www.dian.com;
root /var/www/dian;
index index.php index.html;
include rules/simple_wordpress.conf;
access_log /var/log/nginx/dian.access.log;
error_log /var/log/nginx/dian.error.log;
}
Varnish配置
启动项配置
启动文件配置 /etc/systemd/system/varnish.service.d/customexec.conf
[Service]
ExecStart=
ExecStart=/usr/sbin/varnishd -F -a :6081 -T localhost:6082 -f /etc/varnish/default.vcl -S /etc/varnish/secret -s malloc,1G
vcl配置
vcl文件:/etc/varnish/default.vcl
vcl 4.1;
# import std;
backend default {
.host = "127.0.0.1";
.port = "8080"; # 后端 Nginx
}
# ---------- 统一变量 ----------
acl purge {
"127.0.0.1";
}
# WooCommerce 等不缓存的路径与 AJAX
# 购物车/结账/账户/添加到购物车/wc-ajax/admin-ajax/wp-json 这些都绕过
sub is_uncacheable_req {
if (req.method != "GET" && req.method != "HEAD") {
set req.http.X-Uncacheable = "1";
} elseif (req.url ~ "(?i)/wp-(admin|login)\b" ||
req.url ~ "(?i)preview=true" ||
req.url ~ "(?i)/cart/?$" ||
req.url ~ "(?i)/checkout/?$" ||
req.url ~ "(?i)/my-account\b" ||
req.url ~ "(?i)[?&]add-to-cart=" ||
req.url ~ "(?i)/\?add-to-cart=" ||
req.url ~ "(?i)/\?wc-ajax=" ||
req.url ~ "(?i)/wc-ajax/" ||
req.url ~ "(?i)/wp-admin/admin-ajax.php" ||
req.url ~ "(?i)/wp-json/") {
set req.http.X-Uncacheable = "1";
}
}
# 登录/购物车相关 Cookie (调试购物车、登录后的页面的时候可以打开注释)
# sub has_priv_cookies {
# if (req.http.Cookie ~ "(?i)wordpress_logged_in|woocommerce_items_in_cart|woocommerce_cart_hash|wp_woocommerce_session") {
# set req.http.X-Uncacheable = "1";
# }
# }
# 清理无关 Cookie(仅对可缓存请求)
sub clean_cookies {
if (req.http.Cookie) {
# 保留必要 cookie,删掉其它(统计、sbjs、utm 等)
set req.http.Cookie = regsuball(req.http.Cookie, "(^|;\s*)(_ga|_gid|_fbp|_gcl_au|sbjs_[^=]+|utm_[^=]+)=[^;]*", "\1");
set req.http.Cookie = regsuball(req.http.Cookie, "(^|;\s*)($)", "\1"); # 触发清洗
if (req.http.Cookie == "" || req.http.Cookie == ";") { unset req.http.Cookie; }
}
}
sub vcl_recv {
# PURGE
if (req.method == "PURGE") {
if (!client.ip ~ purge) { return (synth(405, "Not allowed")); }
return (purge);
}
# 不缓存/esi/开头的请求
if (req.url ~ "^/esi/") {
return (pass);
}
# 静态资源:仅保留 ?ver= 作为 key,其它 query 去掉
if (req.url ~ "\.(css|js|jpg|jpeg|png|gif|svg|ico|webp|woff|woff2|ttf|eot)(\?.*)?$") {
if (req.url ~ "\?") {
if (req.url ~ "(?i)[?&]ver=") {
set req.url = regsub(req.url, "^([^?]+)\?.*$", "\1") + "?" +
regsub(req.url, ".*[?&]((?i)ver=[^&]+).*", "\1");
} else {
set req.url = regsub(req.url, "\?.*$", "");
}
}
call clean_cookies; # 静态资源不需要 cookie
return (hash);
}
# 后台/特定路径 或 有私有 Cookie → 不缓存
call is_uncacheable_req;
#(调试购物车、登录后的页面的时候可以打开注释)
# call has_priv_cookies;
if (req.http.X-Uncacheable) {
return (pass);
}
# 其它 GET/HEAD 可缓存页面:清理无关 Cookie
call clean_cookies;
return (hash);
}
sub vcl_backend_response {
# 静态资源:强缓存
if (bereq.url ~ "\.(css|js|jpg|jpeg|png|gif|svg|ico|webp|woff|woff2|ttf|eot)$") {
unset beresp.http.Set-Cookie;
set beresp.ttl = 7d;
set beresp.grace = 1h;
return (deliver);
}
# 不缓存/esi/开头的页面
if (bereq.url ~ "^/esi/") {
# avoid caching user-specific fragments at edge unless explicitly desired
set beresp.ttl = 0s;
set beresp.uncacheable = true;
return (deliver);
}
# HTML:匿名 10 分钟;允许 grace
if (beresp.http.Content-Type ~ "(?i)text/html") {
set beresp.ttl = 10m;
set beresp.grace = 1m;
# 如果未来做 ESI,可在命中某些模板时打开:
set beresp.do_esi = true;
}
if (beresp.ttl <= 0s ||
beresp.http.Set-Cookie ||
beresp.http.Cache-Control ~ "no-cache|private") {
set beresp.uncacheable = true;
return (deliver);
}
# 如果后端瞎发 no-cache,但你确定要缓存,可在这里覆盖(慎用):
# if (beresp.http.Cache-Control ~ "no-cache|no-store") { ... }
# if (beresp.ttl > 0s && beresp.http.Surrogate-Control ~ "ESI/1.0") {
# set beresp.do_esi = true;
# }
}
sub vcl_deliver {
# 命中标记
if (obj.hits > 0) {
set resp.http.X-Cache = "HIT";
} else {
set resp.http.X-Cache = "MISS";
}
# 便于排查
set resp.http.X-Varnish-URL = req.url;
# HSTS(浏览器以后自动用 HTTPS)
set resp.http.Strict-Transport-Security = "max-age=31536000; includeSubDomains; preload";
}
WordPress配置
wp-config.php配置
/**https问题 */
if (!empty($_SERVER['HTTP_X_FORWARDED_PROTO']) && strtolower($_SERVER['HTTP_X_FORWARDED_PROTO']) === 'https') {
$_SERVER['HTTPS'] = 'on';
}
define('FORCE_SSL_ADMIN', true);
// 站点常量建议
define('WP_HOME', 'https://' . $_SERVER['HTTP_HOST']);
define('WP_SITEURL', 'https://' . $_SERVER['HTTP_HOST']);
页面模板的处理
方法1:将页面中个人数据部分剥离出来,替换成<esi:include>标签,然后对这部分数据单独生成。方法见:
方法2:将页面中个人数据部分留空,使用cart_fragments,通过ajax动态填充。