Typecho 在 1.3.0(2026 年发布)这个版本里把路由解析改成了基于 PSR-7 风格的 ServerRequest 处理流程,但伪静态规则的本质没变——核心都是把"看起来是一个静态文件路径的 URL"转给 index.php 接管。原文那篇文章把 Apache、Nginx、SAE、IIS 四种环境的规则一字不差地排在一起,到 2026 年看,至少有三处需要更新:SAE(新浪云应用引擎)已经在 2024 年下半年停止商业运营,那段 config.yaml 没人会再写;IIS 阵营里 ISAPI Rewrite 这种第三方扩展早被微软自家的 URL Rewrite Module(web.config 配置)取代;Nginx 段落里那种嵌套 if (-f ...) 写法被 Nginx 官方明确列为"反模式",业内主流已经全部切到 try_files 一行解决。
这篇会按"为什么要有伪静态、四种主流环境的现代正确写法、www/非 www 与 HTTPS 的 301 协调、Typecho 永久链接的几种格式选择、迁移现有 URL 时怎么 301 不丢权重、伪静态没生效时的排查路线"六块来重写,把 2026 年还在生产里跑的细节讲透。这篇文章用的环境信息是保哥实测过的:服务器宝塔面板单机部署、Nginx 1.24、PHP 7.4、Typecho 1.3.0、永久链接配置为 /[slug].html。
伪静态对 Typecho 来说到底解决什么问题
Typecho 的入口文件 index.php 内部维护一个路由表(在 1.3 版本下你可以在后台"设置 → 永久链接"看到对应的格式串),它把请求路径映射到对应的处理逻辑。如果 PHP 文件直接接收 URL,"请求 index.php?cid=99" 这种带查询字符串的 URL 是它最舒服处理的形式。但用户、搜索引擎、外链平台对这种 URL 的偏好显著低于"看起来是文件路径"的 URL,这就引出了伪静态:让 /typecho-rewrite-rules-301-jump-settings.html 这条 URL 在前端看起来像一个静态 HTML 文件,背后由 web 服务器的 rewrite 模块把它内部转给 index.php 处理。
"伪静态"和"真静态"的区别在于:真静态是 PHP 把页面渲染完后写到磁盘的 .html 文件,下次请求服务器直接返回这个文件,不再进 PHP;伪静态是请求路径"看起来"像静态文件但其实每次都要进 PHP 渲染。Typecho 默认是伪静态。要做真静态需要装额外的 cache 插件,把生成的 HTML 写到 magic 路径里、Nginx try_files 优先返回它,是另一个话题。
SEO 角度,伪静态的好处不在 URL 字符层面(Google 早就声明对 .html 后缀和 ?id=99 一视同仁,不影响排名),而在两个间接价值:第一,URL 携带的 slug 关键词比纯数字 cid 更有语义,对用户点击率(CTR)有正向影响——SERP 结果的标题下方会显示 URL,slug 里有相关关键词时用户更愿意点;第二,外链平台(论坛、社交媒体、邮件签名)显示的 URL 字符长度有限,干净的伪静态 URL 比 ?cid=99 这种好分享。所以在 2026 年,伪静态依然是新建 Typecho 站点的默认推荐配置。
Apache 环境下的现代写法
原文给的 .htaccess 写法是这样的:
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ /index.php/$1 [L]
</IfModule>这段代码是"把所有不存在的文件路径转给 index.php"的标准写法,逻辑没毛病。但生产环境里要补几样:
AllowOverride 必须打开
.htaccess 的存在前提是 Apache 主配置 httpd.conf 或对应虚拟主机段允许它生效:
<Directory "/www/wwwroot/zhangwenbao.com">
AllowOverride All
Require all granted
</Directory>很多新装的 Apache 默认 AllowOverride None,.htaccess 完全被忽略,伪静态不生效。检查方法:在 .htaccess 里写一行 garbage 比如 asdfgh,访问站点应当返回 500(说明 .htaccess 在被读),不返回 500 就是 AllowOverride 没开。
mod_rewrite 必须装载
Debian/Ubuntu 一般 a2enmod rewrite;CentOS/RHEL 检查 /etc/httpd/conf.modules.d/00-base.conf 里 LoadModule rewrite_module 那一行有没有被注释。apachectl -M | grep rewrite 能确认是否在加载。
性能:能放主配置就别放 .htaccess
.htaccess 有一个被低估的性能代价:Apache 每次请求都要遍历从 DocumentRoot 到目标文件的每一级目录、检查每一级是否有 .htaccess、读文件、解析、应用规则。这意味着同样的 rewrite 规则放在 .htaccess 里比放在 <VirtualHost> 段慢 5-15%(Apache 官方文档自己声明的数字)。生产高流量站点应当把规则移到 server config,但前提是你能改主配置——共享虚拟主机环境拿不到 server config 权限,只能用 .htaccess。
完整的现代 .htaccess
把伪静态 + HTTPS 强制 + www 跳转 + 安全头部一起写:
# /www/wwwroot/zhangwenbao.com/.htaccess
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
# HTTP → HTTPS 强制(HSTS 也加上)
RewriteCond %{HTTPS} !=on
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
# www → 非 www(保哥的站选了不带 www;选 www 的反过来写)
RewriteCond %{HTTP_HOST} ^www\.zhangwenbao\.com$ [NC]
RewriteRule ^ https://zhangwenbao.com%{REQUEST_URI} [R=301,L]
# Typecho 伪静态主体
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ /index.php/$1 [L]
</IfModule>
<IfModule mod_headers.c>
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
Header always set X-Content-Type-Options "nosniff"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
</IfModule>关键细节:
www\.zhangwenbao\.com$一定要把点号转义成\.,且加$锚定结尾。原文写^www.zhangwenbao.com没转义点、没锚定,会匹配 wwwxzhangwenbao.com、www.zhangwenbao.com.evil-site.com 等异常 host header,给攻击者构造钓鱼跳转的可乘之机。[NC]flag 让 host 比较忽略大小写,避免 WWW.zhangwenbao.com 这种大写漏判。%{REQUEST_URI}而非$1,因为前者带查询字符串,后者不带,跳转时不丢 query string。- HSTS preload 要慎用,意思是把你的域名提交给 Chrome/Firefox 浏览器内置的 HSTS 列表,从此以后无法降级到 HTTP,撤回需要数月。仅在确认未来不再用 HTTP 时启用。
Nginx 环境下的现代写法(推荐用 try_files)
原文给的 Nginx 段是这样:
location / {
index index.html index.php;
if (-f $request_filename/index.html) {
rewrite (.*) $1/index.html break;
}
if (-f $request_filename/index.php) {
rewrite (.*) $1/index.php;
}
if (!-f $request_filename) {
rewrite (.*) /index.php;
}
}这段在 Nginx 0.x 时代是常见写法,但 Igor Sysoev(Nginx 作者)多次明确说"if is evil"——Nginx 的 if 在 location 块里有许多边缘语义,特别是 if (-f) + rewrite 的组合在某些情况下会绕过 location 内的其他指令、或与子请求交互产生意料之外的结果。社区共识是用 try_files 一行替代:
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name zhangwenbao.com;
root /www/wwwroot/zhangwenbao.com;
index index.php index.html;
# SSL 证书
ssl_certificate /etc/letsencrypt/live/zhangwenbao.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/zhangwenbao.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
# Typecho 伪静态主体(一行解决)
location / {
try_files $uri $uri/ /index.php$is_args$args;
}
# PHP 处理
location ~ \.php($|/) {
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass unix:/run/php/php7.4-fpm.sock;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
}
# HSTS
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Content-Type-Options "nosniff" always;
}
# HTTP → HTTPS + www → 非 www 一起跳
server {
listen 80;
listen [::]:80;
server_name zhangwenbao.com www.zhangwenbao.com;
return 301 https://zhangwenbao.com$request_uri;
}
server {
listen 443 ssl http2;
server_name www.zhangwenbao.com;
ssl_certificate /etc/letsencrypt/live/zhangwenbao.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/zhangwenbao.com/privkey.pem;
return 301 https://zhangwenbao.com$request_uri;
}这版的几个要点:
- try_files $uri $uri/ /index.php$is_args$args:让 Nginx 先看 $uri 是否对应静态文件、再看是否对应目录、都没有就 fallback 到 /index.php 并保留原 query string。
$is_args在有 query 时返回 ?,没有时返回空,避免无 query 的 URL 跳到 /index.php? 这种带尾问号的丑陋形式。 - location ~ \.php($|/):注意是
($|/),匹配 .php 结尾或者 .php/ 后接 path_info(Typecho 1.x 用 PATH_INFO 模式时需要)。如果只写 \.php$ 会让 /index.php/foo 这种 URL 匹配不到 PHP 处理器。 - fastcgi_split_path_info:把 /index.php/foo 拆成 SCRIPT_FILENAME=/index.php 和 PATH_INFO=/foo,Typecho 拿 PATH_INFO 来路由。
- 三段 server:一段处理 HTTPS 主域、一段把 HTTP 全部跳 HTTPS、一段把 www 域跳到非 www。这样既正确又清晰。
- HSTS 的 always:add_header 默认只在 200/204/206/301/302/303/307 响应里加,加
always让 4xx/5xx 也带上 HSTS,是 OWASP 推荐的做法。
HTTP/3、QUIC 和 listen 配置的小坑
2026 年 Nginx 1.25.0+ 内置了 HTTP/3(QUIC)支持。如果服务器装了较新版本,可以升级 listen 段:
listen 443 ssl;
listen [::]:443 ssl;
listen 443 quic reuseport;
listen [::]:443 quic reuseport;
http2 on;
http3 on;
# 让浏览器知道支持 HTTP/3
add_header Alt-Svc 'h3=":443"; ma=86400';注意 reuseport 选项只能写一次,多个 server 块都用 quic 时要小心;http2 on 是 Nginx 1.25+ 的新指令,旧版本继续用 listen 443 ssl http2。
SAE 这条早已过时
原文里的 SAE 段:
handle:
- rewrite: if(!is_dir() && !is_file()) goto "index.php?%{QUERY_STRING}"SAE(新浪云应用引擎)在 2024 年年中已停止商业运营,原有的应用迁移到了新浪云 Cloud 或转向其他云服务。这段 config.yaml 在 2026 年已经没有运行环境。如果你看到老教程还在推 SAE 配置,可以直接跳过。要在 PaaS 上跑 Typecho 的现代选择是阿里云 SAE(Serverless 应用引擎,跟新浪云 SAE 同名但不同公司)、腾讯云 SCF + 函数 PHP runtime,或者直接 Docker 化部署到 Kubernetes,这些都是 nginx + php-fpm 的标准组合,伪静态规则跟上面 Nginx 段相同。
IIS 环境用 web.config 而不是 httpd.ini
原文那段 ISAPI_Rewrite 的 httpd.ini 是 Helicon 公司的 ISAPI Rewrite 模块语法,已经被微软自家的 URL Rewrite Module 取代。后者用 web.config(XML 格式)配置,IIS 7+ 都内置支持。Typecho 在 IIS 上的现代 web.config:
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<system.webServer>
<rewrite>
<rules>
<rule name="HTTP to HTTPS" stopProcessing="true">
<match url=".*" />
<conditions>
<add input="{HTTPS}" pattern="off" />
</conditions>
<action type="Redirect" url="https://{HTTP_HOST}/{R:0}" redirectType="Permanent" />
</rule>
<rule name="WWW to non-WWW" stopProcessing="true">
<match url=".*" />
<conditions>
<add input="{HTTP_HOST}" pattern="^www\.zhangwenbao\.com$" />
</conditions>
<action type="Redirect" url="https://zhangwenbao.com/{R:0}" redirectType="Permanent" />
</rule>
<rule name="Typecho" stopProcessing="true">
<match url=".*" />
<conditions logicalGrouping="MatchAll">
<add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
<add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" />
</conditions>
<action type="Rewrite" url="index.php/{R:0}" />
</rule>
</rules>
</rewrite>
</system.webServer>
</configuration>web.config 的优势:可视化界面在 IIS Manager → URL Rewrite → 双击你的站点能图形化编辑;规则增删改不用重启 IIS;一个 web.config 跟着站点目录走,迁服务器直接拷贝即可。
www 还是非 www?这是个有 SEO 影响的决策
原文同时给了"www → 非 www"和"非 www → www"两个方向的代码,但没给选择标准。保哥的建议(基于过去 5 年帮 30 多个站点做这种迁移):
- 新建站点选非 www。短,分享时少打 4 个字符;URL 长度统计学上短的略胜;现代品牌(Twitter、Facebook、Instagram)几乎全部用非 www。
- 已存在的老站点别折腾。如果当前 Google 已经索引你的 www 版本、外链都指向 www,迁到非 www 等于做一次大规模 301 跳转,要 Google 重新爬一遍、重新认权重,期间 SERP 排名波动 1-3 周是常态。除非有强烈的品牌迁移诉求,老站维持现状最稳。
- 两个版本同时存活是大忌。这是真正的 SEO 自残行为:搜索引擎认为这是两个独立站点、内容完全相同,会判重复内容。做完任一方向的 301 之后,必须确认所有其他入口(包括 robots.txt、sitemap、Google Search Console)也指向同一规范域名。
另外注意:选了非 www 之后,HSTS preload 提交时也要把 includeSubDomains 慎重——它会让所有 *.zhangwenbao.com 都强制 HTTPS,如果你以后想给一个内部子域用 HTTP(极少数场景,比如某个 IoT 设备配置页面),这个开关不能撤回。
从 archives/[cid] 迁到 /[slug].html 时怎么处理老 URL
保哥的站点早期用 Typecho 默认的 /archives/[cid]/,后来改成 /[slug].html。已经索引的旧 URL(被外链、被收藏、被搜索引擎记录)必须通过 301 永久重定向到新 URL,不能丢。完整方案有两种:
方案 A:在 Nginx 用 map 一次性映射
# 在 http {} 段
map $request_uri $redirect_target {
default "";
~^/archives/99/?$ /typecho-rewrite-rules-301-jump-settings.html;
~^/archives/93/?$ /notepad-edit-saved-code-...-solution.html;
# ... 给所有需要迁移的 cid 列上
}
# 在 server {} 段
location / {
if ($redirect_target != "") {
return 301 $redirect_target;
}
try_files $uri $uri/ /index.php$is_args$args;
}map 比一堆 if 性能好,因为它是 hash 查找;500 篇文章列在 map 里 nginx -t 启动时间略增(大概 10-50ms),运行时性能无影响。
方案 B:在 Typecho 的 PHP 入口判定
在 index.php 顶部加一段判定:
if (preg_match('#^/archives/(\d+)/?$#', $_SERVER['REQUEST_URI'], $m)) {
// 查 cid 对应的 slug
$cid = (int)$m[1];
$pdo = new PDO('mysql:host=...');
$slug = $pdo->query("SELECT slug FROM typecho_contents WHERE cid=$cid AND status='publish' AND type='post'")->fetchColumn();
if ($slug) {
header("Location: /{$slug}.html", true, 301);
exit;
}
}这种方式优点是不用列 500 条 map,缺点是每次请求都要查一次数据库。混合方案是 Nginx map 兜住 95% 的高频老 URL,剩下的边缘流量进 PHP 处理。
无论哪种,做完后必须做的事
- 更新 sitemap.xml,只包含新 URL,不再列旧 URL。
- Google Search Console 提交新 sitemap,并保留旧站点属性 90 天观察 301 收录情况。
- Bing Webmaster Tools 同步操作。
- 更新 robots.txt 的 Sitemap: 指令。
- 跑一遍全站爬虫(Screaming Frog 或 Sitebulb)确认所有内链都已更新到新 URL,没有"301 chains"(A → B → C 这种多跳,Google 看重每跳损失一点权重)。
Typecho 永久链接格式的 SEO 比较
Typecho 后台"设置 → 永久链接"里能选的几种格式:
| 格式 | 示例 | SEO 角度评价 |
|---|---|---|
| /archives/[cid]/ | /archives/99/ | 默认值,但 cid 无语义。仅适合不在乎 SEO 或文章 slug 不稳定的站。 |
| /[slug].html | /typecho-rewrite-rules.html | 保哥首选。短、有关键词、像静态文件、不带"archives"等冗余目录层级。 |
| /[category]/[slug].html | /typecho/rewrite-rules.html | 适合分类层级清晰的站。但分类调整时要做 301,且 URL 偏长。 |
| /[year]/[month]/[slug].html | /2026/05/rewrite-rules.html | 典型博客站点格式。优点是同名 slug 在不同月份下不冲突,缺点是日期对 SEO 没帮助还显得"老"——一年前的文章被识别为过时。 |
选 /[slug].html 之后要注意 slug 命名要稳定:发布后再改 slug 等于换 URL,要做 301。Typecho 1.3 后台编辑文章时 slug 字段是可见可改的,但提示用户"如果文章已发布且有外链,慎改"。
伪静态没生效时的排查路线
新装 Typecho 后访问 /some-slug.html 返回 404,多半是伪静态没装好。按这个顺序排查:
- 确认 Typecho 后台启用了"地址重写"。设置 → 永久链接 → 把"启用地址重写功能"打勾保存。这一步的实际作用是让 Typecho 在生成 URL 时不带 ?cid= 参数。
- 访问 /index.php/some-slug.html 看是否正常。如果 /index.php/... 能正常返回内容、/...html 返回 404,那 Typecho 是好的,问题完全在 web 服务器层的 rewrite 规则。
- 确认 web 服务器的 rewrite 模块装了。Apache:
apachectl -M | grep rewrite;Nginx 默认带 rewrite 不用单独装。 - 测试 .htaccess 在被读。在 .htaccess 里加一行 garbage 让 Apache 报 500。
- 看 Apache 错误日志或 nginx error.log。Apache 启用 RewriteLog 能看每条规则匹配过程;Nginx 在 server 段加
error_log /var/log/nginx/zhangwenbao_error.log debug;。 - 用 curl -I 看响应头。
curl -I https://zhangwenbao.com/some-slug.html看 HTTP 状态码、Location 头。404 是 rewrite 没匹配;502 是 PHP-FPM 挂了;500 是 PHP 报错(看 PHP 日志)。 - 测试 nginx -t / apachectl -t。改了配置后没 reload 或者配置语法错误,老规则还在生效。
常见问题解答
Apache 的 .htaccess 改了之后要重启吗?
不要重启 Apache,但要确保浏览器没缓存老的 301 跳转。.htaccess 是每次请求时实时读取的,修改保存后下一个请求就生效,无需 reload Apache。但如果之前的 .htaccess 配过 301,浏览器会把这个 301 缓存到本地,即使你删了规则用户访问还是按老规则跳——这种情况要清浏览器缓存或换无痕模式测试。Nginx 改 nginx.conf 后必须执行 nginx -s reload 让新配置生效。生产服务器永远先 nginx -t 检查语法、再 reload,避免直接 restart 导致服务中断 0.5 秒。Apache 同理,apachectl configtest 通过后再 graceful。
Nginx 那段 try_files 后面的 $is_args$args 是什么意思?
$is_args 在请求带 query string 时返回 ?,没 query 时返回空字符串;$args 是完整的 query string(不带前导 ?)。所以 try_files ... /index.php$is_args$args 的意思是:fallback 到 /index.php,如果原请求带 ?key=value 就保留为 /index.php?key=value。如果只写 /index.php$args,没 query 时会变成 /index.php(结尾无 ?)但路径里没 query string,PHP 收到的 $_GET 是空的——这是没问题的。但有些教程写 /index.php?$args,没 query 时变成 /index.php?,PHP 能处理但 URL 末尾多余的问号让监控工具抓到的 URL 不干净。最稳的写法就是 $is_args$args 二者拼接,自动处理两种情况。
HSTS preload 提交后还能撤回吗?
能撤回,但流程慢且痛苦。HSTS preload 列表(hstspreload.org)一旦提交,Chrome、Firefox、Edge、Safari 把你的域名编译进了浏览器二进制,全球用户的浏览器都会拒绝 HTTP 访问你的站点。要撤回需要:第一步在 hstspreload.org 提交 removal 请求并通过验证(要求你的网站当前没有 HSTS 头部,与原来的预加载状态相反);第二步等浏览器厂商把 removal 编译进下一个 release(Chrome 通常 6-12 周一个 release);第三步用户的浏览器升级到新版本才生效,覆盖率达到 90% 以上要 6-12 个月。这意味着如果你不确定未来要不要永久 HTTPS,不要急着提交 preload。建议先跑 1-2 年纯 HSTS(max-age 31536000),生产稳定后再考虑 preload。
选择 Typecho 永久链接为 /[slug].html 后,slug 用中文还是英文?
用英文。中文 slug 在 URL 里会被浏览器编码成 %E4%B8%AD 这种长 percent-encoded 形式,分享时丑得没法看,复制粘贴到聊天软件容易断行。Google 多年前就声明对中文 URL 和编码 URL 一视同仁排名,但用户体验上英文 slug 完胜。slug 命名风格用 lowercase + dash 分隔(typecho-rewrite-rules-301-jump-settings 这种),不用 underscore(_)也不用 camelCase。Slug 长度控制在 5-8 个英文单词、URL 不超过 60 字符是 SEO 友好的标准。Typecho 后台保存文章时如果你没填 slug,它会从标题自动生成中文 pinyin slug——这个 pinyin 也比纯中文好,但比手写英文 slug 更长、关键词分布也不如手写精准。建议每篇文章手动填英文 slug,10 秒钟的事。
Apache 的 mod_rewrite 和 Nginx 的 rewrite 性能差多少?
实测在保哥本站(Typecho 1.3, 527 篇文章)的同一台服务器上,对同一个 URL 用 ab 压测:Nginx 1.24 + try_files + PHP-FPM 7.4 处理一个普通文章页 QPS 约 350;Apache 2.4 + .htaccess + mod_php 处理同一页 QPS 约 180。差距大约 50%。但这个差距主要不来自 rewrite 模块本身的性能(rewrite 在两边都很轻),来自 Apache .htaccess 的逐目录读取代价 + mod_php 的 worker 模型对比 PHP-FPM。如果 Apache 把 rewrite 规则放进 server config 不用 .htaccess、且换 PHP-FPM 而不是 mod_php,差距会缩小到 10-20%。Apache 在 2026 年还有它的市场(共享主机、Plesk/cPanel),但纯性能场景大家都会选 Nginx,这个对比已经多年了。
301 跳转和 302 跳转在 SEO 上到底有什么区别?
301 是永久重定向,浏览器和搜索引擎都会缓存这个跳转、把外链权重从老 URL 传给新 URL(Google 多年前明确声明 301 传递接近 100% 权重,过去说传递 90% 是误传)。302 是临时重定向,浏览器和搜索引擎不缓存、不传递权重。在"我换了 URL,希望旧链接的权重传给新 URL"的场景里必须用 301。但要注意:301 一旦缓存就难撤——浏览器和爬虫多次访问后都记住这条规则,即使你后台改了规则用户还是跳老路径,要等浏览器缓存过期(通常一周到一个月)或者用户清缓存。所以临时性的跳转(运营活动短期跳到 landing page)一律用 302。Typecho 永久链接迁移这种场景一定是 301。
子目录安装 Typecho(比如 /blog/)的伪静态规则要改吗?
要改 RewriteBase 和路径前缀。Apache .htaccess 放在 /www/wwwroot/zhangwenbao.com/blog/ 下,把 RewriteBase / 改成 RewriteBase /blog/,把 RewriteRule ^(.*)$ /index.php/$1 [L] 改成 RewriteRule ^(.*)$ /blog/index.php/$1 [L]。Nginx 同理,给 /blog/ 路径单独 location /blog/ { try_files $uri $uri/ /blog/index.php$is_args$args; }。还要去 Typecho 后台改"站点地址(URL)"为 https://zhangwenbao.com/blog/,否则 Typecho 生成的链接还是认为站点根目录是 /。子目录方式部署主要适用于"主域已经在跑别的站、博客作为副功能"的场景,新建站点不推荐——子目录方式的 SEO 表现略差于子域,搜索引擎认为子目录是主域的一部分内容,权重共享是好事但权重稀释也是事实。
Typecho 后台启用了地址重写,但前台 URL 还是 ?cid=99 怎么办?
多半是 Typecho 在生成内部链接时探测到当前 server 没有 rewrite 能力,自动 fallback 到 query string 模式。检查:第一,确保 .htaccess(Apache)或 nginx 配置(Nginx)已经正确部署且生效,访问 /some-slug.html 能拿到内容。第二,Typecho 在 install 阶段会自动尝试给一个虚拟 URL 看是否能 rewrite 成功,如果当时服务器配置不对,它把"无法重写"的状态写进了数据库 typecho_options 表的 rewrite 字段为 0。后台再次启用 rewrite 应当会重新探测。第三,最暴力的方法是直接在数据库里 UPDATE typecho_options SET value='1' WHERE name='rewrite',然后清后台缓存(设置 → 评论 → 保存任意一项触发缓存刷新)。第四,确认 Typecho 在 config.inc.php 里没有人为关闭 __TYPECHO_REWRITE__ 之类的常量。
HTTPS 强制后 mixed content 报错(控制台说 blocked: mixed-content)怎么处理?
这是页面在 HTTPS 下加载了 HTTP 资源(图片、JS、CSS、iframe)触发的浏览器安全策略。Chrome 在 mixed content 上越来越严,2026 年默认完全阻止 active mixed content(脚本、iframe),passive mixed content(图片、视频)会自动升级到 HTTPS 失败时降级警告。修复策略:第一步全站搜索硬编码的 http:// 资源 URL 替换为 https:// 或 //(协议相对 URL,自动跟当前协议一致)。第二步 Typecho 的图片附件路径要确保是相对路径或站内 https,编辑器里早期插入的 http://zhangwenbao.com/upload/... 全要改。第三步外链的图片(图床、第三方 CDN)必须是 HTTPS 站,否则只能下载到本站再用。第四步在响应头加 Content-Security-Policy: upgrade-insecure-requests,让浏览器自动把 http:// 资源升级为 https:// 加载(如果对方支持的话)。第五步 Chrome DevTools 的 Console 会列出所有 mixed content 报错,逐个修复。
nginx try_files 失败时怎么 debug?
启用 rewrite log 是最直观的方法:在 server 块里加 error_log /var/log/nginx/site_debug.log debug; rewrite_log on;。下次请求会把每一步 try_files 的查找过程写进日志,能看到 trying to use file/dir/fallback。一些常见的 try_files 失效原因:第一,root 路径写错或权限不对,导致 nginx 进程读不到文件——nginx 一般 www-data 用户,文件 chmod 644 + 父目录 755 是基础。第二,try_files 顺序错——把 /index.php 放在前面会让所有请求都进 PHP,静态文件也走 PHP。第三,PHP 处理 location 没匹配上——location ~ \.php($|/) 写成 \.php$ 会让 PATH_INFO 模式失效。第四,fastcgi_pass 路径错或 PHP-FPM 没在跑——sock 路径写错或者 listen 在 IP 上而不是 sock。第五,try_files 后面的 fallback 必须以 / 开头,写 try_files $uri $uri/ index.php 是无效的(必须 /index.php)。逐项排查通常 5 分钟内能定位。