Skip to content

ISP SNI Filtering & Caddy Troubleshooting (RETRACTED)

RETRACTED 2026-07-21 — the ISP SNI-filtering diagnosis was wrong

The conclusion below ("Google Fiber filters SNI hostnames containing wiki") does not hold:

  • wiki.majorshouse.com works today. It has served the Kiwix offline-Wikipedia mirror since 2026-07-12, on the same home IP, through the same ISP.
  • Off-LAN re-verification (2026-07-21): from a Hetzner host (traffic inbound through Google Fiber), openssl s_client -servername wiki.majorshouse.com completes the handshake — valid cert, HTTP 200. The exact test that "proved" the filter now passes.
  • The likely real root cause was a stale DNS A record. During the 2026-07-12 Kiwix setup, an undocumented wiki.majorshouse.com A record was found pointing at a dead IP (136.61.58.247, created 2026-02-25 — before the original March incident). Handshakes against a wrong/dead host produce exactly the observed connection resets. The 2026-04-30 update below independently documents the same stale-A-record failure mode taking down notes.majorshouse.com — and diagnosed it correctly that time.

The hostname rename to notes.majorshouse.com "fixed" the issue coincidentally: the new name got a fresh, correct DNS record. The original text is preserved below as a record of the misdiagnosis. Lesson: before blaming the network path, verify what the hostname actually resolves to — and test from off-LAN before attributing anything to the ISP.

Update 2026-11-02 — the Cloudflare proxy workaround is removed. The 2026-04-30 section below kept notes behind the Cloudflare proxy partly to dodge the (now-retracted) SNI filter. That's no longer needed: notes is now a plain DNS-only CNAME to majorshouse.com, the same as every other home-hosted subdomain. Off-LAN check after the change: HTTP 200 served directly by Caddy, with a valid Let's Encrypt cert. The CNAME-to-apex part of that fix still holds. The "proxied" part, and the matching checklist item, are superseded.

🛑 Problem

When deploying the MajorWiki at wiki.majorshouse.com, the site was unreachable over HTTPS. Browsers reported a TLS_CONNECTION_REFUSED error.

🔍 Diagnosis

  1. Direct IP Check: Accessing the server via IP on port 8092 worked fine.
  2. Tailscale Check: Accessing via the Tailscale magic DNS worked fine.
  3. SNI Analysis: Using openssl s_client -connect <IP>:443 -servername wiki.majorshouse.com resulted in an immediate reset by peer.
  4. Root Cause: Google Fiber (the local ISP) appears to be performing SNI-based filtering on hostnames containing the string "wiki".

✅ Solution

The domain was changed from wiki.majorshouse.com to notes.majorshouse.com.

Caddy Configuration Update

notes.majorshouse.com {
    reverse_proxy :8092
}

Once the hostname was changed to one without the "wiki" keyword, the TLS handshake completed successfully.


🔁 2026-04-30 Update — Stale A Record + Cloudflare Proxy Fix

The hostname rename held for ~4 weeks. On 2026-04-30 the wiki went down with a TLS handshake failure on notes.majorshouse.com. The on-the-spot framing was "ISP filter expanded to include 'notes'" — but Cloudflare DNS audit showed a different (and arguably worse) root cause: the notes A record was pointing at 136.54.3.248, an IP that is not majorlab's current home IP. Whichever host responds at that address either does not run Caddy or does not know about the notes.majorshouse.com SNI, so the TLS handshake was rejected with internal_error 80.

Re-diagnosis

# Cert + Caddy + mkdocs all healthy on majorlab
$ ssh majorlab 'systemctl is-active caddy; ss -tlnp | grep :443'
active
LISTEN 0  4096  *:443  users:(("caddy",pid=1549,fd=7))

# Loopback-served TLS works fine — cert valid Mar 11 → Jun 9 2026
$ ssh majorlab 'curl -sS -o /dev/null -w "%{http_code}\n" --resolve notes.majorshouse.com:443:127.0.0.1 https://notes.majorshouse.com/'
200

# External TLS handshake gets rejected with internal_error
$ openssl s_client -servername notes.majorshouse.com -connect 136.54.3.248:443
… SSL alert number 80 (internal_error) …

The smoking-gun comparison

Other *.majorshouse.com services worked because they were CNAMEs to the apex, which resolves to majorlab's actual home IP:

Subdomain DNS shape Final IP Status
notes.majorshouse.com A → 136.54.3.248 (stale) 136.54.3.248 (wrong host) ❌ TLS rejected
git.majorshouse.com CNAME → majorshouse.com. 136.56.0.55 (majorlab) ✅
n8n.majorshouse.com CNAME → majorshouse.com. 136.56.0.55 (majorlab) ✅
matrix.majorshouse.com CNAME → majorshouse.com. 136.56.0.55 (majorlab) ✅

None of the working subdomains were proxied through Cloudflare (proxied=false on all of them); they simply had the right IP. The notes A record was the only one pointing somewhere wrong — most likely a stale value from a prior ISP / IP change that never got cleaned up.

✅ Fix — switch notes to a Cloudflare-proxied CNAME

Rather than just correcting the A record (which would silently break again the next time the home IP changes), the fix is a CNAME to the apex with proxy on. That gives two protections in one move: it always tracks the apex (so home IP changes propagate automatically) and it puts the wiki behind Cloudflare's edge (so any future ISP-side weirdness like the original wiki SNI filter is also bypassed).

# via Cloudflare API (token from ansible-vault: vault_cloudflare_api_token)
PUT /zones/{ZONE_ID}/dns_records/{NOTES_RECORD_ID}
{
  "type":    "CNAME",
  "name":    "notes.majorshouse.com",
  "content": "majorshouse.com",
  "ttl":     1,
  "proxied": true,
  "comment": "switched A→CNAME proxied to bypass stale IP / ISP SNI filter"
}

Or via the dashboard:

  1. Cloudflare → majorshouse.com zone → DNS → Records
  2. Edit the notes record: Type CNAME, Target majorshouse.com, Proxy Proxied (orange cloud)
  3. Save

External clients now hit Cloudflare edge IPs (104.21.x.x / 172.67.x.x) which TLS-terminate at the edge and tunnel back to majorlab's apex IP. ACME on majorlab keeps working — Cloudflare passes the HTTP-01 challenge through on port 80. Caddy's notes.majorshouse.com {} block needs no change.

Verify (response should show server: cloudflare and via: 1.0 Caddy):

curl -sSI https://notes.majorshouse.com/

Why a Cloudflare-proxied CNAME is the durable shape

  • Apex follows the home IP automatically. Update the apex A record once when the ISP changes; every subdomain inherits it without per-record fixes.
  • TLS handshake is offloaded to CF. Any ISP-level SNI weirdness (the original wiki ban; theoretical future bans) becomes irrelevant — external clients SNI=notes.majorshouse.com to Cloudflare, which the ISP doesn't filter.
  • Free. Cloudflare's free tier covers proxy + TLS termination.

Audit checklist for any home-hosted *.majorshouse.com subdomain

  • [ ] DNS record is a CNAME to majorshouse.com., not an A record to a literal home IP.
  • [ ] ~~Cloudflare proxy (orange cloud, proxied=true) enabled on the record — at minimum for any subdomain where TLS reachability matters.~~ Superseded 2026-11-02: leave the record DNS-only (proxied=false); the SNI premise was retracted.
  • [ ] Caddy entry on majorlab references the public hostname; reverse_proxy stays on the localhost port.
  • [ ] HTTPS verified from outside the LAN (phone on cellular is sufficient) within the first hour after the change.
  • [ ] If an A record is genuinely required (e.g. it must NOT go through CF), document why in the deploy notes for that service.