Hotdry.
application-security

WordPress URL 重写代理外部图片:近零成本自托管交付

利用插件或代码重写外部图片 URL 为本地代理路径,实现按需缓存与自托管交付,避开存储开销。

在 WordPress 站点中嵌入外部图片常见于内容聚合或快速发布,但外部图片往往加载缓慢、易失效,且依赖第三方服务影响站点自主性。更严重的是,图片 URL 来自外部域名,会稀释 SEO 权重,并增加跨域风险。本文聚焦单一技术点:通过 URL 重写将外部图片代理到自托管路径,实现近零成本交付 —— 仅缓存热门图片,按需下载,避免全量存储开销。

方案核心原理

传统自托管需批量下载所有图片,占用海量存储(单站点 TB 级)。优化路径:浏览器端保持原 URL(SEO 无损),服务端通过 rewrite/filter 拦截 img src,重定向到本地 proxy 端点。Proxy 脚本动态拉取外部图片,生成缓存副本(基于 URL hash),设置 TTL 过期。优点:

  • 零存储开销起步:首次访问才下载,热门图自然缓存,冷门图代理即丢。
  • 自托管外观:图片从本站域名交付,提升 LCP 分数。
  • 兼容性强:不改数据库,支持缓存插件如 WP Super Cache。

证据支持:类似 CDN Enabler 插件通过内容替换重写 URL 到 CDN,“CDN Enabler 捕获页面内容并重写由指定 CDN 提供服务的 URL”。我们改本地 proxy,即 “自 CDN”。

实现步骤:插件 vs 纯代码

1. 插件方案(推荐新手,CDN Enabler 变体)

安装 CDN Enabler,设置中填本地 proxy URL 如 https://your-site.com/proxy/ 前缀。但默认推 CDN,我们自定义:

  • 设置 “CDN URL” 为 /images/proxy/(相对路径)。
  • 包含扩展:jpg,jpeg,png,gif,webp,avif。
  • 排除:本站域名(如 your-site.com)。 激活后,页面输出时 img src 自动重写为 /images/proxy/https%3A//external.com/img.jpg

需配套 proxy.php(见下)。

2. 纯代码方案(functions.php,无插件依赖)

在当前主题 functions.php 末尾添加:

// 重写外部图片 URL 为 proxy
add_filter('the_content', 'rewrite_external_images');
add_filter('widget_text_content', 'rewrite_external_images'); // 小工具
function rewrite_external_images($content) {
    if (is_feed() || is_admin()) return $content; // 排除 RSS/后台
    $site_host = parse_url(home_url(), PHP_URL_HOST);
    $content = preg_replace_callback(
        '/<img[^>]+src=["\']([^"\']+)["\'][^>]*>/i',
        function($matches) use ($site_host) {
            $src = $matches[1];
            $img_host = parse_url($src, PHP_URL_HOST);
            if (!$img_host || $img_host === $site_host) return $matches[0]; // 跳本地
            $proxy_url = '/images/proxy/?url=' . urlencode($src);
            return str_replace($src, $proxy_url, $matches[0]);
        },
        $content
    );
    return $content;
}

此 filter 在渲染时替换,支持 Gutenberg/CSS 背景图需扩展 wp_filter_content_tags

3. Proxy 脚本部署(核心,uploads/images/proxy.php)

wp-content/uploads/images/ 创建 proxy.php(目录 755,文件 644):

<?php
// 防止直接访问
if (!isset($_GET['url'])) exit('Invalid request');
$url = urldecode($_GET['url']);
if (!preg_match('/\.(jpg|jpeg|png|gif|webp|avif)$/i', $url)) exit('Invalid image');

// 缓存逻辑
$hash = md5($url);
$cache_dir = __DIR__ . '/cache/';
$cache_file = $cache_dir . $hash . '.img';
$ttl = 3600; // 1小时

if (file_exists($cache_file) && (time() - filemtime($cache_file)) < $ttl) {
    $img = file_get_contents($cache_file);
} else {
    $ctx = stream_context_create(['http' => ['timeout' => 10, 'user_agent' => 'Mozilla/5.0']]);
    $img = @file_get_contents($url, false, $ctx);
    if ($img === false || strlen($img) > 5*1024*1024) exit('Fetch failed or too large'); // 限 5MB
    @mkdir($cache_dir, 0755, true);
    file_put_contents($cache_file, $img);
}

// 输出
$ext = strtolower(pathinfo(parse_url($url, PHP_URL_PATH), PATHINFO_EXTENSION));
$mime = ['jpg'=>'image/jpeg', 'jpeg'=>'image/jpeg', 'png'=>'image/png', 'gif'=>'image/gif', 'webp'=>'image/webp', 'avif'=>'image/avif'][$ext] ?? 'image/jpeg';
header('Content-Type: ' . $mime);
header('Cache-Control: public, max-age=3600');
header('Access-Control-Allow-Origin: *');
echo $img;
?>

Nginx/Apache 加 rewrite:/images/proxy/(.*)/wp-content/uploads/images/proxy.php?url=$1

重启服务,清缓存测试:外部图变 /images/proxy/?url=...,加载自站。

可落地参数与清单

  • 缓存 TTL:3600s(1h),热门图命中率高;监控日志调整至 7200s。
  • 尺寸阈值:5MB 上限,避大图爆存;可选 ImageMagick 压缩。
  • 排除列表['cdn.cloudflare.com', 'your-site.com'],防循环。
  • 目录配置
    参数 说明
    缓存路径 wp-content/uploads/images/cache/ 按月分区
    权限 755 (dir)/644 (file) 防执行
    清理脚本 cron 每日删过期 wp-cron 或 server cron
  • 性能参数:PHP timeout 10s;Redis 缓存元数据(可选)。

监控要点与回滚策略

  • 指标:proxy 日志命中率(>80% 成功);存增长 <10GB / 月;LCP <2.5s(Lighthouse)。
  • 告警:存 >80% 触发清理;错误率 >5% 查 UA/Referer。
  • 回滚:注释 filter,删 proxy.php,flush rewrite rules(flush_rewrite_rules())。 风险:代理带宽峰值(限流 Nginx limit_req);兼容 AMP(加 nosniff)。

此方案落地后,站点图片 100% 自域交付,成本仅带宽费。类似 ImgCache 插件验证有效:“缓存外部图片到本地,避免域名阻塞”。

资料来源

(正文 1250+ 字)

查看归档