使用Hitch+Varnish+WordPress时HTTPS网站混合内容错误求助
问题
我的网站启用HTTPS后出现混合内容错误,架构为:443端口流量经Hitch TLS代理转发至Varnish,最终到达Apache Web Server。排查发现网站首次请求以HTTPS加载,但后续所有请求均由Varnish直接以HTTP处理,被浏览器判定为混合内容拦截。已尝试Varnish的HTTP转HTTPS重定向配置,以及在wp-config.php中设置WordPress强制HTTPS常量,均未解决问题,求如何实现客户端所有URL通过TLS加载。
现有配置文件
Hitch TLS Proxy配置文件
# Upstream server address. backend = "[127.0.0.1]:8443" pem-file = { cert = "/etc/letsencrypt/live/staging1.sainikbiswas.com/fullchain.pem" private-key = "/etc/letsencrypt/live/staging1.sainikbiswas.com/privkey.pem" } ocsp-dir = "/var/lib/hitch-ocsp" ciphersuites = "TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256" # List of allowed TLS ciphers. ciphers = "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHAA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RRSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384" # Enforce server cipher list order # # type: boolean prefer-server-ciphers = off tls-protos = TLSv1.2 TLSv1.3 alpn-protos = "h2, http/1.1" workers = 4 backlog = 100 keepalive = 3600 chroot = "" user = "hitch" group = "hitch" syslog = on syslog-facility = "daemon" daemon = on write-ip = off write-proxy-v1 = off write-proxy-v2 = on proxy-proxy = off sni-nomatch-abort = off
Apache 2配置文件
<VirtualHost *:8080> ServerName staging1.sainikbiswas.com ServerAdmin john@doe.com DocumentRoot "/home/sainikbiswas/domains/sainikbiswas-com/public" <Directory /home/sainikbiswas/domains/sainikbiswas-com/public> #Disable .htaccess file AllowOverride None #WordPress Rewrite Rules <IfModule mod_rewrite.c> RewriteEngine On RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] RewriteBase / RewriteRule ^index\.php$ - [L] RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule . /index.php [L] </IfModule> #Disable File Index Options -Indexes -FollowSymLinks +SymLinksIfOwnerMatch #Enable Access to the Document Root Require all granted </Directory> </VirtualHost>
Varnish配置文件
vcl 4.1; import proxy; import std; # Default backend definition. Set this to point to your content server. backend default { .host = "127.0.0.1"; .port = "8080"; } # Add hostnames, IP addresses and subnets that are allowed to purge content acl purge { "localhost"; "127.0.0.1"; "::1"; } sub vcl_recv { if ((req.http.X-Forwarded-Proto && req.http.X-Forwarded-Proto != "https") || (req.http.Scheme && req.http.Scheme != "https")) { return (synth(750)); } elseif (!req.http.X-Forwarded-Proto && !req.http.Scheme && !proxy.is_ssl()) { return (synth(750)); } # WordPress Varnish Configuration # Remove empty query string parameters # e.g.: www.example.com/index.html? if (req.url ~ "\?$") { set req.url = regsub(req.url, "\?$", ""); } # Remove port number from host header set req.http.Host = regsub(req.http.Host, ":[0-9]+", ""); # Sorts query string parameters alphabetically for cache normalization purposes set req.url = std.querysort(req.url); # Remove the proxy header to mitigate the httpoxy vulnerability # See https://httpoxy.org/ unset req.http.proxy; # Add X-Forwarded-Proto header when using https if(!req.http.X-Forwarded-Proto) { if (proxy.is_ssl()) { set req.http.X-Forwarded-Proto = "https"; } else { set req.http.X-Forwarded-Proto = "http"; } } # Purge logic to remove objects from the cache. # Tailored to the Proxy Cache Purge WordPress plugin # See https://wordpress.org/plugins/varnish-http-purge/ if(req.method == "PURGE") { if(!client.ip ~ purge) { return(synth(405,"PURGE not allowed for this IP address")); } if (req.http.X-Purge-Method == "regex") { ban("obj.http.x-url ~ " + req.url + " && obj.http.x-host == " + req.http.host); return(synth(200, "Purged")); } ban("obj.http.x-url == " + req.url + " && obj.http.x-host == " + req.http.host); return(synth(200, "Purged")); } # Only handle relevant HTTP request methods if ( req.method != "GET" && req.method != "HEAD" && req.method != "PUT" && req.method != "POST" && req.method != "PATCH" && req.method != "TRACE" && req.method != "OPTIONS" && req.method != "DELETE" ) { return (pipe); } # Remove tracking query string parameters used by analytics tools if (req.url ~ "(\?|&)(utm_source|utm_medium|utm_campaign|utm_content|gclid|cx|ie|cof|siteurl)=") { set req.url = regsuball(req.url, "&(utm_source|utm_medium|utm_campaign|utm_content|gclid|cx|ie|cof|siteurl)=([A-z0-9_\-\.%25]+)", ""); set req.url = regsuball(req.url, "\?(utm_source|utm_medium|utm_campaign|utm_content|gclid|cx|ie|cof|siteurl)=([A-z0-9_\-\.%25]+)", "?"); set req.url = regsub(req.url, "\?&", "?"); set req.url = regsub(req.url, "\?$", ""); } # Only cache GET and HEAD requests if (req.method != "GET" && req.method != "HEAD") { set req.http.X-Cacheable = "NO:REQUEST-METHOD"; return(pass); # Mark static files with the X-Static-File header, and remove any cookies # X-Static-File is also used in vcl_backend_response to identify static files if (req.url ~ "^[^?]*\.(7z|avi|bmp|bz2|css|csv|doc|docx|eot|flac|flv|gif|gz|ico|jpeg|jpg|js|less|mka|mkv|mov|mp3|mp4|mpeg|mpg|odt|ogg|ogm|opus|otf|pdf|png|ppt|pptx|rar|rtf|svg|svgz|swf|tar|tbz|tgz|ttf|txt|txz|wav|webm|webp|woff|woff2|xls|xlsx|xml|xz|zip)(\?.*)?$") { set req.http.X-Static-File = "true"; unset req.http.Cookie; return(hash); } # No caching of special URLs, logged in users and some plugins if ( req.http.Cookie ~ "wordpress_(?!test_)[a-zA-Z0-9_]+|wp-postpass|comment_author_[a-zA-Z0-9_]+|woocommerce_cart_hash|woocommerce_items_in_cart|wp_woocommerce_session_[a-zA-Z0-9]+|wordpress_logged_in_|comment_author|PHPSESSID" || req.http.Authorization || req.url ~ "add_to_cart" || req.url ~ "edd_action" || req.url ~ "nocache" || req.url ~ "^/addons" || req.url ~ "^/bb-admin" || req.url ~ "^/bb-login.php" || req.url ~ "^/bb-reset-password.php" || req.url ~ "^/cart" || req.url ~ "^/checkout" || req.url ~ "^/control.php" || req.url ~ "^/login" || req.url ~ "^/logout" || req.url ~ "^/lost-password" || req.url ~ "^/my-account" || req.url ~ "^/product" || req.url ~ "^/register" || req.url ~ "^/register.php" || req.url ~ "^/server-status" || req.url ~ "^/signin" || req.url ~ "^/signup" || req.url ~ "^/stats" || req.url ~ "^/wc-api" || req.url ~ "^/wp-admin" || req.url ~ "^/wp-comments-post.php" || req.url ~ "^/wp-cron.php" || req.url ~ "^/wp-login.php" || req.url ~ "^/wp-activate.php" || req.url ~ "^/wp-mail.php" || req.url ~ "^/wp-login.php" || req.url ~ "^\?add-to-cart=" || req.url ~ "^\?wc-api=" || req.url ~ "^/preview=" || req.url ~ "^/\.well-known/acme-challenge/" ) { set req.http.X-Cacheable = "NO:Logged in/Got Sessions"; if(req.http.X-Requested-With == "XMLHttpRequest") { set req.http.X-Cacheable = "NO:Ajax"; } return(pass); } # Remove any cookies left unset req.http.Cookie; return(hash); } sub vcl_synth { if (resp.status == 750) { set resp.status = 301; set resp.http.location = "https://" + req.http.Host + req.url; set resp.reason = "Moved"; return (deliver); } } sub vcl_hash { #WordPress Varnish Configuration if(req.http.X-Forwarded-Proto) { # Create cache variations depending on the request protocol hash_data(req.http.X-Forwarded-Proto); } } sub vcl_backend_response { if(beresp.http.Vary) { set beresp.http.Vary = beresp.http.Vary + ", X-Forwarded-Proto"; } else { set beresp.http.Vary = "X-Forwarded-Proto"; } # Inject URL & Host header into the object for asynchronous banning purposes set beresp.http.x-url = bereq.url; set beresp.http.x-host = bereq.http.host; # If we dont get a Cache-Control header from the backend # we default to 1h cache for all objects if (!beresp.http.Cache-Control) { set beresp.ttl = 1h; set beresp.http.X-Cacheable = "YES:Forced"; } # If the file is marked as static we cache it for 1 day if (bereq.http.X-Static-File == "true") { unset beresp.http.Set-Cookie; set beresp.http.X-Cacheable = "YES:Forced"; set beresp.ttl = 1d; } # Remove the Set-Cookie header when a specific Wordfence cookie is set if (beresp.http.Set-Cookie ~ "wfvt_|wordfence_verifiedHuman") { unset beresp.http.Set-Cookie; } if (beresp.http.Set-Cookie) { set beresp.http.X-Cacheable = "NO:Got Cookies"; } elseif(beresp.http.Cache-Control ~ "private") { set beresp.http.X-Cacheable = "NO:Cache-Control=private"; } } sub vcl_deliver { # Debug header if(req.http.X-Cacheable) { set resp.http.X-Cacheable = req.http.X-Cacheable; } elseif(obj.uncacheable) { if(!resp.http.X-Cacheable) { set resp.http.X-Cacheable = "NO:UNCACHEABLE"; } } elseif(!resp.http.X-Cacheable) { set resp.http.X-Cacheable = "YES"; } # Cleanup of headers unset resp.http.x-url; unset resp.http.x-host; }
尝试过的Varnish重定向配置
vcl 4.1; import proxy; backend default { .host = "127.0.0.1"; .port = 8080; } sub vcl_recv { if ((req.http.X-Forwarded-Proto && req.http.X-Forwarded-Proto != "https") || (req.http.Scheme && req.http.Scheme != "https")) { return (synth(750)); } elseif (!req.http.X-Forwarded-Proto && !req.http.Scheme && !proxy.is_ssl()) { return (synth(750)); } } sub vcl_synth { if (resp.status == 750) { set resp.status = 301; set resp.http.location = "https://" + req.http.Host + req.url; set resp.reason = "Moved"; return (deliver); } }
尝试过的WordPress配置
define( 'WP_HOME', 'https://staging1.sainikbiswas.com' ); define( 'WP_SITEURL', 'https://staging1.sainikbiswas.com');
解决方案
1. 确认Varnish监听PROXY协议端口
Hitch已启用write-proxy-v2 = on,需修改Varnish启动参数,让其监听8443端口并识别PROXY协议:
-a :8443,PROXY
此设置确保Varnish能正确获取Hitch传递的SSL连接状态。
2. 调整Varnish的协议头处理顺序
当前VCL中X-Forwarded-Proto的设置在重定向检查之后,导致判断逻辑失效。修改vcl_recv,先设置协议头再做重定向检查:
sub vcl_recv { # 优先设置X-Forwarded-Proto if(!req.http.X-Forwarded-Proto) { if (proxy.is_ssl()) { set req.http.X-Forwarded-Proto = "https"; } else { set req.http.X-Forwarded-Proto = "http"; } } # 强制HTTPS重定向 if (req.http.X-Forwarded-Proto != "https") { return (synth(750)); } # 保留原有其余代码... }
3. 配置Apache识别HTTPS请求
修改Apache虚拟主机配置,让PHP和WordPress知道请求实际使用HTTPS:
<VirtualHost *:8080> # 原有配置... # 传递HTTPS状态给PHP SetEnvIf X-Forwarded-Proto "https" HTTPS=on # 后端强制HTTPS重定向 <IfModule mod_rewrite.c> RewriteEngine On RewriteCond %{HTTP:X-Forwarded-Proto} !https RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301] </IfModule> </VirtualHost>
4. 完善WordPress HTTPS配置
在wp-config.php中添加额外常量:
define('FORCE_SSL_ADMIN',
相关产品推荐
相关产品推荐

