You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用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 ~ "\?$&quot;) {
        set req.url = regsub(req.url, "\?$&quot;, "");
    }

    # 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, "\?$&quot;, "");
    }

    # 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',
相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.07 01:31:09