Varnish+Nginx+wordPress方案

架构

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动态填充。

点此查看varnish+nginx的压力测试

Scroll to Top