From 063f992516625011f160864f01ac499645bd4abb Mon Sep 17 00:00:00 2001 From: AnHonestHost Dev Date: Sat, 1 Aug 2026 17:27:59 -0700 Subject: [PATCH] chore: version-control the WHMCS KB redirect rules MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit These 301s live in .htaccess on secure.anhonesthost.com and existed only on that server. WHMCS rewrites .htaccess during some updates, which would silently drop them — every retired KB URL would start 404ing with nothing in version control to restore from. deploy/README.md documents where it installs, how to reinstall, and the two traps: article/category IDs collide (only the trailing .html tells them apart, so rule order is load-bearing) and the slug is ignored. Co-Authored-By: Claude Opus 5 (1M context) --- deploy/README.md | 59 ++++++++++++++++++ deploy/whmcs-kb-redirects.conf | 107 +++++++++++++++++++++++++++++++++ 2 files changed, 166 insertions(+) create mode 100644 deploy/README.md create mode 100644 deploy/whmcs-kb-redirects.conf diff --git a/deploy/README.md b/deploy/README.md new file mode 100644 index 0000000..44f6d76 --- /dev/null +++ b/deploy/README.md @@ -0,0 +1,59 @@ +# Deploy artefacts + +Config that lives on other servers but belongs under version control here, +because it exists to serve this KB. + +## `whmcs-kb-redirects.conf` + +301 redirects that send the retired WHMCS knowledgebase +(`secure.anhonesthost.com/knowledgebase/*`) to its replacement pages on +kb.anhonesthost.com. Installed 2026-08-01. + +**Where it lives in production:** `/home/whmcs/public_html/.htaccess` on +`secure.anhonesthost.com`, prepended **before** the +`### BEGIN - WHMCS managed rules ###` marker. It has to come first — WHMCS's +own block ends with a catch-all that routes everything to `index.php`, so +rules placed after it never run. + +The canonical copy on that server is `/root/kb-redirects.conf`, and the +pre-migration `.htaccess` is backed up at +`/root/htaccess-backup-20260801.bak`. + +### Reinstalling + +WHMCS rewrites `.htaccess` during some updates, which will silently drop +these rules. To restore: + +```bash +cd /home/whmcs/public_html +cp -a .htaccess /root/htaccess-backup-$(date +%Y%m%d).bak # keep whatever WHMCS wrote +cat /root/kb-redirects.conf /root/htaccess-backup-$(date +%Y%m%d).bak > /tmp/htaccess.new +install -o whmcs -g whmcs -m 644 /tmp/htaccess.new .htaccess +apachectl -t +``` + +Then spot-check a redirect: + +```bash +curl -sI https://secure.anhonesthost.com/knowledgebase/13/x.html | grep -i location +# expect: https://kb.anhonesthost.com/support/remote-support/ +``` + +### Two things that will bite you when editing it + +**Article and category IDs collide.** Both use the shape +`/knowledgebase//`, and the same number means different things — +article 5 is "What are my Name Servers?", category 5 is "WordPress Specific". +The *only* discriminator is the trailing `.html` on articles. That's why every +article rule appears before every category rule and terminates with `[L]`: by +the time the broad category patterns run, anything ending `.html` is already +gone. Reordering the file breaks this silently, and the wrong page still +returns 200. + +**The slug is ignored.** WHMCS reads only the id, so any slug with the right +id resolves. The rules match `[^/]*` for the slug for the same reason. + +Legacy pre-SEO URLs (`knowledgebase.php?action=displayarticle&id=N` and +`?action=displaycat&catid=N`) are mapped explicitly. `(^|&)id=` cannot match +`catid=` — the preceding character is `t`, not `&` or start-of-string — so the +two sets can't cross-fire. diff --git a/deploy/whmcs-kb-redirects.conf b/deploy/whmcs-kb-redirects.conf new file mode 100644 index 0000000..c2eb754 --- /dev/null +++ b/deploy/whmcs-kb-redirects.conf @@ -0,0 +1,107 @@ +### BEGIN - KB migration redirects to kb.anhonesthost.com ### +# Added 2026-08-01. The WHMCS knowledgebase is retired; these 301s send its +# URLs to the equivalent page on the dedicated KB. +# +# ORDER MATTERS. Article and category URLs share the shape +# /knowledgebase// +# and their IDs COLLIDE (article 5 is "What are my Name Servers?", category 5 +# is "WordPress Specific"). The only discriminator is the trailing ".html" on +# articles. Article rules therefore come first and terminate with [L]; the +# category rules below can then match broadly because anything ending .html +# has already been redirected and will never reach them. +# +# The slug is ignored by WHMCS (only the id is read), so it is ignored here +# too — any slug with the right id resolves to the right destination. +# +# The trailing "?" on each target discards the inbound query string. + +RewriteEngine on +RewriteBase / + +# --- Articles (.html) ------------------------------------------------- +RewriteRule ^knowledgebase/2/[^/]*\.html$ https://kb.anhonesthost.com/email/set-up-your-email-client/? [R=301,L] +RewriteRule ^knowledgebase/3/[^/]*\.html$ https://kb.anhonesthost.com/email/set-up-your-email-client/? [R=301,L] +RewriteRule ^knowledgebase/4/[^/]*\.html$ https://kb.anhonesthost.com/email/spam-filtering-changes/? [R=301,L] +RewriteRule ^knowledgebase/5/[^/]*\.html$ https://kb.anhonesthost.com/cpanel/nameservers/? [R=301,L] +RewriteRule ^knowledgebase/6/[^/]*\.html$ https://kb.anhonesthost.com/cpanel/fix-a-403-error/? [R=301,L] +RewriteRule ^knowledgebase/7/[^/]*\.html$ https://kb.anhonesthost.com/cpanel/wordpress-email-from-your-domain/? [R=301,L] +RewriteRule ^knowledgebase/8/[^/]*\.html$ https://kb.anhonesthost.com/cpanel/free-ssl-certificate/? [R=301,L] +RewriteRule ^knowledgebase/9/[^/]*\.html$ https://kb.anhonesthost.com/email/set-up-your-email-client/? [R=301,L] +RewriteRule ^knowledgebase/10/[^/]*\.html$ https://kb.anhonesthost.com/domains/transferring-a-domain-to-us/? [R=301,L] +RewriteRule ^knowledgebase/11/[^/]*\.html$ https://kb.anhonesthost.com/domains/transferring-a-domain-to-us/? [R=301,L] +RewriteRule ^knowledgebase/12/[^/]*\.html$ https://kb.anhonesthost.com/domains/flush-your-dns-cache/? [R=301,L] +RewriteRule ^knowledgebase/13/[^/]*\.html$ https://kb.anhonesthost.com/support/remote-support/? [R=301,L] +RewriteRule ^knowledgebase/14/[^/]*\.html$ https://kb.anhonesthost.com/whp/getting-started/welcome/? [R=301,L] + +# --- Categories (no .html) -------------------------------------------- +RewriteRule ^knowledgebase/2/[^/]*/?$ https://kb.anhonesthost.com/email/set-up-your-email-client/? [R=301,L] +RewriteRule ^knowledgebase/3/[^/]*/?$ https://kb.anhonesthost.com/email/spam-filtering-changes/? [R=301,L] +RewriteRule ^knowledgebase/4/[^/]*/?$ https://kb.anhonesthost.com/cpanel/nameservers/? [R=301,L] +RewriteRule ^knowledgebase/5/[^/]*/?$ https://kb.anhonesthost.com/cpanel/wordpress-email-from-your-domain/? [R=301,L] +RewriteRule ^knowledgebase/6/[^/]*/?$ https://kb.anhonesthost.com/domains/transferring-a-domain-to-us/? [R=301,L] +RewriteRule ^knowledgebase/7/[^/]*/?$ https://kb.anhonesthost.com/cpanel/fix-a-403-error/? [R=301,L] +RewriteRule ^knowledgebase/8/[^/]*/?$ https://kb.anhonesthost.com/support/remote-support/? [R=301,L] +RewriteRule ^knowledgebase/9/[^/]*/?$ https://kb.anhonesthost.com/whp/getting-started/welcome/? [R=301,L] + +# --- Legacy query-string URLs ----------------------------------------- +# Pre-SEO-URL links still in old tickets and emails: +# knowledgebase.php?action=displayarticle&id= +# knowledgebase.php?action=displaycat&catid= +# WHMCS used to 301 these to the friendly URL itself, but our rules above +# now intercept first — so map them explicitly or they all land on the KB +# home page and lose their destination. +# +# "(^|&)id=" cannot match "catid=" (the preceding char is "t", not & or +# start-of-string), so the two sets can't cross-fire. +RewriteCond %{QUERY_STRING} (^|&)id=2(&|$) +RewriteRule ^knowledgebase\.php$ https://kb.anhonesthost.com/email/set-up-your-email-client/? [R=301,L] +RewriteCond %{QUERY_STRING} (^|&)id=3(&|$) +RewriteRule ^knowledgebase\.php$ https://kb.anhonesthost.com/email/set-up-your-email-client/? [R=301,L] +RewriteCond %{QUERY_STRING} (^|&)id=4(&|$) +RewriteRule ^knowledgebase\.php$ https://kb.anhonesthost.com/email/spam-filtering-changes/? [R=301,L] +RewriteCond %{QUERY_STRING} (^|&)id=5(&|$) +RewriteRule ^knowledgebase\.php$ https://kb.anhonesthost.com/cpanel/nameservers/? [R=301,L] +RewriteCond %{QUERY_STRING} (^|&)id=6(&|$) +RewriteRule ^knowledgebase\.php$ https://kb.anhonesthost.com/cpanel/fix-a-403-error/? [R=301,L] +RewriteCond %{QUERY_STRING} (^|&)id=7(&|$) +RewriteRule ^knowledgebase\.php$ https://kb.anhonesthost.com/cpanel/wordpress-email-from-your-domain/? [R=301,L] +RewriteCond %{QUERY_STRING} (^|&)id=8(&|$) +RewriteRule ^knowledgebase\.php$ https://kb.anhonesthost.com/cpanel/free-ssl-certificate/? [R=301,L] +RewriteCond %{QUERY_STRING} (^|&)id=9(&|$) +RewriteRule ^knowledgebase\.php$ https://kb.anhonesthost.com/email/set-up-your-email-client/? [R=301,L] +RewriteCond %{QUERY_STRING} (^|&)id=10(&|$) +RewriteRule ^knowledgebase\.php$ https://kb.anhonesthost.com/domains/transferring-a-domain-to-us/? [R=301,L] +RewriteCond %{QUERY_STRING} (^|&)id=11(&|$) +RewriteRule ^knowledgebase\.php$ https://kb.anhonesthost.com/domains/transferring-a-domain-to-us/? [R=301,L] +RewriteCond %{QUERY_STRING} (^|&)id=12(&|$) +RewriteRule ^knowledgebase\.php$ https://kb.anhonesthost.com/domains/flush-your-dns-cache/? [R=301,L] +RewriteCond %{QUERY_STRING} (^|&)id=13(&|$) +RewriteRule ^knowledgebase\.php$ https://kb.anhonesthost.com/support/remote-support/? [R=301,L] +RewriteCond %{QUERY_STRING} (^|&)id=14(&|$) +RewriteRule ^knowledgebase\.php$ https://kb.anhonesthost.com/whp/getting-started/welcome/? [R=301,L] + +RewriteCond %{QUERY_STRING} (^|&)catid=2(&|$) +RewriteRule ^knowledgebase\.php$ https://kb.anhonesthost.com/email/set-up-your-email-client/? [R=301,L] +RewriteCond %{QUERY_STRING} (^|&)catid=3(&|$) +RewriteRule ^knowledgebase\.php$ https://kb.anhonesthost.com/email/spam-filtering-changes/? [R=301,L] +RewriteCond %{QUERY_STRING} (^|&)catid=4(&|$) +RewriteRule ^knowledgebase\.php$ https://kb.anhonesthost.com/cpanel/nameservers/? [R=301,L] +RewriteCond %{QUERY_STRING} (^|&)catid=5(&|$) +RewriteRule ^knowledgebase\.php$ https://kb.anhonesthost.com/cpanel/wordpress-email-from-your-domain/? [R=301,L] +RewriteCond %{QUERY_STRING} (^|&)catid=6(&|$) +RewriteRule ^knowledgebase\.php$ https://kb.anhonesthost.com/domains/transferring-a-domain-to-us/? [R=301,L] +RewriteCond %{QUERY_STRING} (^|&)catid=7(&|$) +RewriteRule ^knowledgebase\.php$ https://kb.anhonesthost.com/cpanel/fix-a-403-error/? [R=301,L] +RewriteCond %{QUERY_STRING} (^|&)catid=8(&|$) +RewriteRule ^knowledgebase\.php$ https://kb.anhonesthost.com/support/remote-support/? [R=301,L] +RewriteCond %{QUERY_STRING} (^|&)catid=9(&|$) +RewriteRule ^knowledgebase\.php$ https://kb.anhonesthost.com/whp/getting-started/welcome/? [R=301,L] + +# --- Tags, search, index, and anything else under /knowledgebase ------ +# Catch-all last: any KB URL not matched above lands on the KB home page +# rather than a WHMCS 404. +RewriteRule ^knowledgebase\.php$ https://kb.anhonesthost.com/? [R=301,L] +RewriteRule ^knowledgebase(/.*)?$ https://kb.anhonesthost.com/? [R=301,L] + +### END - KB migration redirects to kb.anhonesthost.com ### + -- 2.52.0