{"id":810,"date":"2026-05-02T07:00:26","date_gmt":"2026-05-02T01:30:26","guid":{"rendered":"https:\/\/www.cyberaka.com\/?p=810"},"modified":"2026-05-02T07:00:27","modified_gmt":"2026-05-02T01:30:27","slug":"when-googling-my-own-product-sent-visitors-to-a-prayer-app-a-debugging-story-with-my-ai-pair","status":"publish","type":"post","link":"https:\/\/www.cyberaka.com\/?p=810","title":{"rendered":"When Googling My Own Product Sent Visitors to a Prayer App: A Debugging Story With My AI Pair"},"content":{"rendered":"<div id=\"ez-toc-container\" class=\"ez-toc-v2_0_82_2 counter-hierarchy ez-toc-counter ez-toc-grey ez-toc-container-direction\">\n<p class=\"ez-toc-title\" style=\"cursor:inherit\">Table of Contents<\/p>\n<label for=\"ez-toc-cssicon-toggle-item-69f5c206b0bf0\" class=\"ez-toc-cssicon-toggle-label\"><span class=\"\"><span class=\"eztoc-hide\" style=\"display:none;\">Toggle<\/span><span class=\"ez-toc-icon-toggle-span\"><svg style=\"fill: #999;color:#999\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" class=\"list-377408\" width=\"20px\" height=\"20px\" viewBox=\"0 0 24 24\" fill=\"none\"><path d=\"M6 6H4v2h2V6zm14 0H8v2h12V6zM4 11h2v2H4v-2zm16 0H8v2h12v-2zM4 16h2v2H4v-2zm16 0H8v2h12v-2z\" fill=\"currentColor\"><\/path><\/svg><svg style=\"fill: #999;color:#999\" class=\"arrow-unsorted-368013\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"10px\" height=\"10px\" viewBox=\"0 0 24 24\" version=\"1.2\" baseProfile=\"tiny\"><path d=\"M18.2 9.3l-6.2-6.3-6.2 6.3c-.2.2-.3.4-.3.7s.1.5.3.7c.2.2.4.3.7.3h11c.3 0 .5-.1.7-.3.2-.2.3-.5.3-.7s-.1-.5-.3-.7zM5.8 14.7l6.2 6.3 6.2-6.3c.2-.2.3-.5.3-.7s-.1-.5-.3-.7c-.2-.2-.4-.3-.7-.3h-11c-.3 0-.5.1-.7.3-.2.2-.3.5-.3.7s.1.5.3.7z\"\/><\/svg><\/span><\/span><\/label><input type=\"checkbox\"  id=\"ez-toc-cssicon-toggle-item-69f5c206b0bf0\"  aria-label=\"Toggle\" \/><nav><ul class='ez-toc-list ez-toc-list-level-1 ' ><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-1\" href=\"https:\/\/www.cyberaka.com\/?p=810\/#The_setup_that_broke_my_Friday_afternoon\" >The setup that broke my Friday afternoon<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-2\" href=\"https:\/\/www.cyberaka.com\/?p=810\/#The_architecture_and_a_quick_glossary\" >The architecture (and a quick glossary)<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-3\" href=\"https:\/\/www.cyberaka.com\/?p=810\/#Bug_1_wwwquizwrapcom_was_serving_the_prayer_app\" >Bug #1: www.quizwrap.com was serving the prayer app<\/a><ul class='ez-toc-list-level-3' ><li class='ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-4\" href=\"https:\/\/www.cyberaka.com\/?p=810\/#The_detective_work\" >The detective work<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-5\" href=\"https:\/\/www.cyberaka.com\/?p=810\/#Reading_the_configs_over_SSH\" >Reading the configs over SSH<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-6\" href=\"https:\/\/www.cyberaka.com\/?p=810\/#The_fix\" >The fix<\/a><\/li><\/ul><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-7\" href=\"https:\/\/www.cyberaka.com\/?p=810\/#Bug_2_smartdishacoin_and_the_certificate_that_wouldnt_come_right\" >Bug #2: smartdisha.co.in and the certificate that wouldn\u2019t come right<\/a><ul class='ez-toc-list-level-3' ><li class='ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-8\" href=\"https:\/\/www.cyberaka.com\/?p=810\/#The_symptom\" >The symptom<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-9\" href=\"https:\/\/www.cyberaka.com\/?p=810\/#Two_hours_of_dead_ends\" >Two hours of dead ends<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-10\" href=\"https:\/\/www.cyberaka.com\/?p=810\/#My_moment_of_skepticism\" >My moment of skepticism<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-11\" href=\"https:\/\/www.cyberaka.com\/?p=810\/#The_breakthrough_probing_SNI_directly\" >The breakthrough: probing SNI directly<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-12\" href=\"https:\/\/www.cyberaka.com\/?p=810\/#The_actual_root_cause\" >The actual root cause<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-13\" href=\"https:\/\/www.cyberaka.com\/?p=810\/#The_fix-2\" >The fix<\/a><\/li><\/ul><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-14\" href=\"https:\/\/www.cyberaka.com\/?p=810\/#What_it_was_actually_like_to_debug_this_with_an_AI\" >What it was actually like to debug this with an AI<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-15\" href=\"https:\/\/www.cyberaka.com\/?p=810\/#Lessons_that_generalise\" >Lessons that generalise<\/a><\/li><\/ul><\/nav><\/div>\n<h2 id=\"the-setup-that-broke-my-friday-afternoon\"><span class=\"ez-toc-section\" id=\"The_setup_that_broke_my_Friday_afternoon\"><\/span>The setup that broke my Friday afternoon<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>I was checking my own SEO. I typed <em>\u201cquizwrap\u201d<\/em> into Google. My site, <a href=\"https:\/\/www.quizwrap.com\/\">QuizWrap<\/a> \u2014 a free quiz-maker for students \u2014 showed up as the very first result. Great.<\/p>\n<p>I clicked it.<\/p>\n<p>A <strong>Ho\u2019oponopono prayer counter<\/strong> loaded.<\/p>\n<p>That\u2019s a completely different app I run on the same server, and visitors looking for QuizWrap were landing on it instead. Worse, I quickly noticed a related issue: visiting <code>https:\/\/smartdisha.co.in\/<\/code> directly threw a TLS certificate error in the browser.<\/p>\n<p>Two bugs, both on the same VPS, both involving the nginx reverse proxy that fronts everything. I sat down with Claude (Anthropic\u2019s coding agent inside Claude Code) and we dug in together. What follows is the story of that debugging session \u2014 both the technical findings and what it was like to pair-debug with an AI.<\/p>\n<hr \/>\n<h2 id=\"the-architecture-and-a-quick-glossary\"><span class=\"ez-toc-section\" id=\"The_architecture_and_a_quick_glossary\"><\/span>The architecture (and a quick glossary)<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>A quick mental model so the rest of this makes sense.<\/p>\n<p>A single VPS hosts three sites behind one system <strong>nginx<\/strong> \u2014 a popular web server that, in this setup, acts as a <em>reverse proxy<\/em>: a traffic cop sitting in front that takes incoming HTTPS requests and forwards them to the right internal app.<\/p>\n<ul>\n<li><strong>quizwrap.com<\/strong> \u2014 my quiz app<\/li>\n<li><strong>prayer.quizwrap.com<\/strong> \u2014 a small prayer counter<\/li>\n<li><strong>smartdisha.co.in<\/strong> \u2014 a separate site on the same box<\/li>\n<\/ul>\n<p>Some traffic flows through a CDN before reaching origin, some doesn\u2019t. Each domain has its own free <strong>Let\u2019s Encrypt<\/strong> TLS certificate (the thing that makes the little padlock icon appear in your browser), and nginx is configured with one server block per domain.<\/p>\n<p>A few terms I\u2019ll keep using:<\/p>\n<ul>\n<li><strong>TLS<\/strong> \u2014 the encryption layer behind HTTPS. The \u201cS\u201d in HTTPS.<\/li>\n<li><strong>Certificate<\/strong> \u2014 a small file that proves a server owns the domain it claims to. Browsers reject the connection if the cert doesn\u2019t match the domain.<\/li>\n<li><strong>SNI (Server Name Indication)<\/strong> \u2014 the most important term in this whole post. When your browser opens a TLS connection to <code>smartdisha.co.in<\/code>, it whispers the hostname it wants <em>before<\/em> the encryption is set up, so the server knows which certificate to present. One server can host many domains on the same IP, and SNI is how it picks the right cert. If SNI says one thing and the server returns the wrong cert, the browser shows a security warning and refuses to load the page.<\/li>\n<li><strong>Server block<\/strong> \u2014 nginx\u2019s term for \u201cthe config chunk that handles requests for one domain.\u201d Each domain has one (or several).<\/li>\n<li><strong><code>server_name<\/code> directive<\/strong> \u2014 the line <em>inside<\/em> a server block that lists which hostnames that block is responsible for. If no block claims a hostname, nginx silently picks a default block as a fallback.<\/li>\n<\/ul>\n<hr \/>\n<h2 id=\"bug-1-www.quizwrap.com-was-serving-the-prayer-app\"><span class=\"ez-toc-section\" id=\"Bug_1_wwwquizwrapcom_was_serving_the_prayer_app\"><\/span>Bug #1: <code>www.quizwrap.com<\/code> was serving the prayer app<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<h3 id=\"the-detective-work\"><span class=\"ez-toc-section\" id=\"The_detective_work\"><\/span>The detective work<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>Before touching anything, Claude pulled response headers from both URLs in parallel:<\/p>\n<pre class=\"bash\"><code>curl -sI https:\/\/www.quizwrap.com\/\ncurl -sI https:\/\/smartdisha.co.in\/<\/code><\/pre>\n<p>Two response bodies came back with <strong>identical fingerprints<\/strong>:<\/p>\n<table>\n<thead>\n<tr>\n<th><\/th>\n<th>www.quizwrap.com<\/th>\n<th>smartdisha.co.in<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td><code>ETag<\/code><\/td>\n<td><code>\"69d2087a-332\"<\/code><\/td>\n<td><code>\"69d2087a-332\"<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>Content-Length<\/code><\/td>\n<td>818<\/td>\n<td>818<\/td>\n<\/tr>\n<tr>\n<td><code>Last-Modified<\/code><\/td>\n<td>same date<\/td>\n<td>same date<\/td>\n<\/tr>\n<tr>\n<td>Title in body<\/td>\n<td><code>Ho'oponopono Counter<\/code><\/td>\n<td><code>Ho'oponopono Counter<\/code><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>(An <code>ETag<\/code> is a unique fingerprint a web server attaches to a file\u2019s response \u2014 like a checksum. Two responses with the same <code>ETag<\/code> are byte-for-byte the same file.)<\/p>\n<p>Same file, served to two different domains. Now we knew it was an nginx routing question, not a DNS or CDN issue.<\/p>\n<h3 id=\"reading-the-configs-over-ssh\"><span class=\"ez-toc-section\" id=\"Reading_the_configs_over_SSH\"><\/span>Reading the configs over SSH<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>I had Claude SSH into my server (passwordless key auth \u2014 read-only operations, no sudo) and dump the three nginx configs. The first thing it spotted:<\/p>\n<pre class=\"nginx\"><code># \/etc\/nginx\/sites-available\/quizwrap.com\nserver {\n    server_name quizwrap.com;\n    ...\n}<\/code><\/pre>\n<p><code>server_name quizwrap.com<\/code> \u2014 not <code>quizwrap.com www.quizwrap.com<\/code>. There was <strong>no server block anywhere on the box claiming <code>www.quizwrap.com<\/code><\/strong>. When a request arrived at my server saying <em>\u201cthis is for <code>www.quizwrap.com<\/code>\u201d<\/em>, nginx had no rule that named that hostname, so it fell back to the first SSL block in alphabetical order \u2014 the one for <code>prayer.quizwrap.com<\/code>, which is what serves the prayer app.<\/p>\n<p>That\u2019s how a Google click on <code>www.quizwrap.com<\/code> ended up rendering Ho\u2019oponopono. nginx was doing exactly what it was told; what it was told just didn\u2019t include the <code>www<\/code> version of my domain.<\/p>\n<h3 id=\"the-fix\"><span class=\"ez-toc-section\" id=\"The_fix\"><\/span>The fix<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>A one-liner:<\/p>\n<pre class=\"bash\"><code>sudo sed -i 's\/server_name quizwrap.com;\/server_name quizwrap.com www.quizwrap.com;\/' \\\n  \/etc\/nginx\/sites-available\/quizwrap.com\nsudo nginx -t &amp;&amp; sudo systemctl reload nginx<\/code><\/pre>\n<p>A test confirmed it:<\/p>\n<pre><code>HTTP\/2 200\nlast-modified: Sun, 30 Nov 2025 15:42:27 GMT   \u2190 quizwrap build, not the prayer one\n&lt;title&gt;QuizWrap - FREE Study Quiz Maker for Students&lt;\/title&gt;<\/code><\/pre>\n<p>Then a defensive follow-up: re-issue the Let\u2019s Encrypt cert to cover the <code>www<\/code> version too, so the cert chain stays internally consistent. (A single cert can list multiple hostnames in a field called the <strong>Subject Alternative Name<\/strong>, or SAN \u2014 that\u2019s just \u201cthe list of domains this cert is valid for.\u201d) One certbot command added <code>www.quizwrap.com<\/code> to the cert. Done.<\/p>\n<p><strong>Bug #1: 5 minutes from \u201cwhat is happening\u201d to \u201cfixed.\u201d<\/strong><\/p>\n<p>Bug #2 was <em>not<\/em> like that.<\/p>\n<hr \/>\n<h2 id=\"bug-2-smartdisha.co.in-and-the-certificate-that-wouldnt-come-right\"><span class=\"ez-toc-section\" id=\"Bug_2_smartdishacoin_and_the_certificate_that_wouldnt_come_right\"><\/span>Bug #2: <code>smartdisha.co.in<\/code> and the certificate that wouldn\u2019t come right<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<h3 id=\"the-symptom\"><span class=\"ez-toc-section\" id=\"The_symptom\"><\/span>The symptom<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>Browsers refused <code>https:\/\/smartdisha.co.in\/<\/code> with a cert error. <code>openssl s_client<\/code> showed why:<\/p>\n<pre class=\"bash\"><code>$ echo | openssl s_client -servername smartdisha.co.in -connect smartdisha.co.in:443 2&gt;\/dev\/null \\\n    | openssl x509 -noout -subject -ext subjectAltName\n\nsubject=CN = prayer.quizwrap.com\nDNS:prayer.quizwrap.com<\/code><\/pre>\n<p>The browser asked for <code>smartdisha.co.in<\/code> (via SNI), and the server handed back a certificate that says <em>\u201cI\u2019m <code>prayer.quizwrap.com<\/code>.\u201d<\/em> That\u2019s a name mismatch, so the browser refuses the connection \u2014 you\u2019ve probably seen the resulting \u201cYour connection is not private\u201d error page. At first I thought the fix was going to be just as quick as the <code>www<\/code> one.<\/p>\n<p>It wasn\u2019t.<\/p>\n<h3 id=\"two-hours-of-dead-ends\"><span class=\"ez-toc-section\" id=\"Two_hours_of_dead_ends\"><\/span>Two hours of dead ends<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>Here\u2019s the parade of \u201cthat should have fixed it\u201d:<\/p>\n<ol type=\"1\">\n<li><strong>Re-issue the cert?<\/strong> <code>sudo certbot --nginx -d smartdisha.co.in<\/code> \u2014 certbot reported there <em>was<\/em> an existing cert and offered to reinstall. Reinstalled. <strong>No change.<\/strong> Browser still got prayer\u2019s cert.<\/li>\n<li><strong>Maybe nginx didn\u2019t reload cleanly.<\/strong> <code>sudo systemctl reload nginx<\/code>. <strong>No change.<\/strong><\/li>\n<li><strong>Inspect the cert file directly.<\/strong>\n<pre class=\"bash\"><code>sudo openssl x509 -in \/etc\/letsencrypt\/live\/smartdisha.co.in\/fullchain.pem \\\n    -noout -subject -ext subjectAltName\nsubject=CN = smartdisha.co.in\nDNS:smartdisha.co.in<\/code><\/pre>\n<p>The file on disk was <strong>correct<\/strong>. nginx just wasn\u2019t serving it.<\/li>\n<li><strong>Maybe the workers cached an old cert.<\/strong> <code>sudo systemctl restart nginx<\/code>. <strong>No change.<\/strong><\/li>\n<li><strong>Check <code>nginx -T<\/code> for the loaded config.<\/strong> The smartdisha SSL block was fully loaded, with the right <code>server_name<\/code>, the right <code>listen 443 ssl;<\/code>, and the right cert path. Everything looked correct. <strong>Still no change.<\/strong><\/li>\n<\/ol>\n<p>At one point I checked <code>ps<\/code> and noticed <em>three<\/em> nginx master processes \u2014 two with <code>nginx -g daemon off;<\/code> (the Docker-container telltale) and one system nginx. Claude initially flagged this as the smoking gun: maybe a Docker container was intercepting TLS. We confirmed via <code>ss -tlnp<\/code> that the system nginx was actually the only thing on port 443; the Docker nginxes were just internal app servers behind it. <strong>Wrong turn \u2014 but a reasonable one.<\/strong><\/p>\n<h3 id=\"my-moment-of-skepticism\"><span class=\"ez-toc-section\" id=\"My_moment_of_skepticism\"><\/span>My moment of skepticism<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>I sent Claude a screenshot of my DNS panel with the message:<\/p>\n<blockquote><p><em>\u201cBefore we go chase our tail. Check the configuration attached.\u201d<\/em><\/p><\/blockquote>\n<p>This was the right instinct. I was tired of theories that weren\u2019t panning out. Stepping back to verify a load-bearing assumption \u2014 <em>is the request path for this domain actually what we think it is?<\/em> \u2014 confirmed we were looking at the right place, but it could just as easily have caught us going the wrong way for another hour.<\/p>\n<p><strong>Lesson:<\/strong> when you\u2019re three theories deep and none have stuck, your AI assistant doesn\u2019t always notice it\u2019s in a loop. Pushing back is your job.<\/p>\n<h3 id=\"the-breakthrough-probing-sni-directly\"><span class=\"ez-toc-section\" id=\"The_breakthrough_probing_SNI_directly\"><\/span>The breakthrough: probing SNI directly<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>Claude wrote a small loop that asked nginx, in plain English: <em>\u201cIf a browser tells you it wants hostname X, which certificate do you hand back?\u201d<\/em> It does this once for each domain on the box.<\/p>\n<pre class=\"bash\"><code>ssh my-server 'for sni in &lt;each-hostname-on-the-box&gt;; do\n  printf \"SNI=%-30s -&gt; \" \"$sni\"\n  echo | openssl s_client -servername \"$sni\" -connect localhost:443 2&gt;\/dev\/null \\\n    | openssl x509 -noout -subject 2&gt;\/dev\/null\ndone'<\/code><\/pre>\n<pre><code>SNI=smartdisha.co.in           -&gt; CN = prayer.quizwrap.com    \u274c\nSNI=www.quizwrap.com           -&gt; CN = quizwrap.com            \u2713\nSNI=quizwrap.com               -&gt; CN = quizwrap.com            \u2713\nSNI=prayer.quizwrap.com        -&gt; CN = prayer.quizwrap.com     \u2713\nSNI=nonexistent.example.com    -&gt; CN = prayer.quizwrap.com     (default fallback)<\/code><\/pre>\n<p>There it was. <strong><code>smartdisha.co.in<\/code> was being treated identically to a totally unknown hostname.<\/strong> It wasn\u2019t a cert problem at all \u2014 the cert file on disk was perfectly fine. nginx just wasn\u2019t <em>recognizing<\/em> <code>smartdisha.co.in<\/code> as a hostname it knew about. Both unknown hostnames and <code>smartdisha.co.in<\/code> fell through to the same default fallback block (<code>prayer<\/code>, which is alphabetically first), which is why both got prayer\u2019s cert.<\/p>\n<h3 id=\"the-actual-root-cause\"><span class=\"ez-toc-section\" id=\"The_actual_root_cause\"><\/span>The actual root cause<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>With that clue, Claude re-read all three nginx configs side-by-side and found the only structural difference:<\/p>\n<table>\n<thead>\n<tr>\n<th>Block<\/th>\n<th>IPv6 listen<\/th>\n<th>IPv4 listen<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>prayer<\/td>\n<td><code>listen [::]:443 ssl ipv6only=on;<\/code><\/td>\n<td><code>listen 443 ssl;<\/code><\/td>\n<\/tr>\n<tr>\n<td>quizwrap<\/td>\n<td><code>listen [::]:443 ssl;<\/code> <em>(dual-stack)<\/em><\/td>\n<td><code>listen 443 ssl;<\/code><\/td>\n<\/tr>\n<tr>\n<td>smartdisha<\/td>\n<td><strong>\u2014 missing \u2014<\/strong><\/td>\n<td><code>listen 443 ssl;<\/code><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>A bit of background to read that table: every server on the internet has two kinds of addresses available \u2014 older <strong>IPv4<\/strong> (the familiar <code>1.2.3.4<\/code> style) and newer <strong>IPv6<\/strong> (the longer <code>::1<\/code> style). nginx\u2019s <code>listen<\/code> directive tells it which addresses to accept connections on. <code>listen 443 ssl;<\/code> means \u201cIPv4 only.\u201d <code>listen [::]:443 ssl;<\/code> means \u201cIPv6,\u201d but on Linux it can also quietly accept IPv4 connections at the same time \u2014 that\u2019s what \u201cdual-stack\u201d means.<\/p>\n<p><code>quizwrap<\/code>\u2019s <code>listen [::]:443 ssl;<\/code> (without <code>ipv6only=on<\/code>) creates one of these dual-stack sockets. Internally, nginx groups server blocks by which socket they\u2019re attached to, and uses that grouping to decide who handles each incoming connection. <code>smartdisha<\/code>, lacking any IPv6 listen line of its own, ends up in a different group than the dual-stack one, and inside that group the <code>prayer<\/code> block (alphabetically first) becomes the default catch-all. Even though <code>smartdisha<\/code>\u2019s server block is loaded and looks correct, the grouping means SNI lookups for <code>smartdisha.co.in<\/code> arrive at a group where smartdisha isn\u2019t listed \u2014 and fall back to prayer.<\/p>\n<p>Subtle, weird, and exactly the kind of thing <code>nginx -t<\/code> (the config syntax checker) won\u2019t catch, because the syntax is fine.<\/p>\n<h3 id=\"the-fix-1\"><span class=\"ez-toc-section\" id=\"The_fix-2\"><\/span>The fix<span class=\"ez-toc-section-end\"><\/span><\/h3>\n<p>Make <code>smartdisha<\/code>\u2019s listen directives match the others:<\/p>\n<pre class=\"bash\"><code>sudo sh -c '\n  cp \/etc\/nginx\/sites-available\/smartdisha.co.in \/etc\/nginx\/sites-available\/smartdisha.co.in.bak\n  sed -i \"\/^    listen 443 ssl; # managed by Certbot$\/i\\\\    listen [::]:443 ssl;\" \\\n    \/etc\/nginx\/sites-available\/smartdisha.co.in\n  nginx -t &amp;&amp; systemctl reload nginx &amp;&amp; echo DONE\n'<\/code><\/pre>\n<p>Re-running the SNI probe afterwards:<\/p>\n<pre><code>SNI=smartdisha.co.in           -&gt; CN = smartdisha.co.in        \u2713<\/code><\/pre>\n<p><code>curl https:\/\/smartdisha.co.in\/<\/code> succeeded with full TLS validation, no <code>-k<\/code> flag needed. The browser was happy.<\/p>\n<hr \/>\n<h2 id=\"what-it-was-actually-like-to-debug-this-with-an-ai\"><span class=\"ez-toc-section\" id=\"What_it_was_actually_like_to_debug_this_with_an_AI\"><\/span>What it was actually like to debug this with an AI<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>A few things stood out about the collaboration that I want to share.<\/p>\n<p><strong>Claude was great at the things I\u2019m bad at.<\/strong> It pulled response headers from two domains in parallel, parsed cert subjects out of <code>openssl s_client<\/code> output, and noticed <em>immediately<\/em> that two responses had the same <code>ETag<\/code> \u2014 something I\u2019d have read past. The structured diff between three nginx configs at the end (the listener-table comparison) was exactly the kind of thing my eyes glaze over after the second config file.<\/p>\n<p><strong>I was great at the things Claude is bad at.<\/strong> When we got stuck on Bug #2, Claude proposed three theories in a row, each plausible, none correct. The Docker-container theory in particular was a confidently-stated wrong answer. <em>I<\/em> knew that side of my own infrastructure well enough to be unimpressed. My push-back (\u201cbefore we chase our tail\u201d) was what reset the direction.<\/p>\n<p><strong>Security boundaries actually got enforced.<\/strong> When I offered Claude my sudo password to speed things up, it explicitly refused and explained why (the password would be in the chat transcript, in shell process listings, and a single leak compromises the whole server). It walked me through the alternatives \u2014 running the destructive commands myself in my own terminal, or scoping a passwordless sudoers rule for nginx-related commands only. Reading the full advice, I ended up just running each <code>sudo<\/code> command in my own shell and pasting the result. Slower, but at no point did a privileged credential cross a boundary it shouldn\u2019t.<\/p>\n<p><strong>Transparency mattered.<\/strong> Halfway through Bug #2 I told Claude <em>\u201cI can\u2019t see the commands you\u2019re executing on my server.\u201d<\/em> It immediately listed every SSH command it had run and committed to printing each new command before executing it. That changed the dynamic \u2014 it stopped feeling like Claude was off doing things in the dark and started feeling like a teammate sharing their screen.<\/p>\n<p><strong>Knowing when to escalate to a one-shot fix.<\/strong> After multiple roundtrips of \u201cpaste this, paste that,\u201d I asked Claude to drive over SSH so I could stop copy-pasting. It moved the read-only diagnostics to its own SSH connection and packaged the <em>one<\/em> mutating step into a single sudo block I could paste once and approve once. The friction of the back-and-forth dropped massively.<\/p>\n<hr \/>\n<h2 id=\"lessons-that-generalise\"><span class=\"ez-toc-section\" id=\"Lessons_that_generalise\"><\/span>Lessons that generalise<span class=\"ez-toc-section-end\"><\/span><\/h2>\n<p>A few things I\u2019m taking away from this:<\/p>\n<ol type=\"1\">\n<li><strong>Identical <code>ETag<\/code>s across two domains = the same file is being served.<\/strong> If two of your sites unexpectedly look the same, that single header probably solves the mystery before you read a line of config.<\/li>\n<li><strong><code>server_name<\/code> is a registration, not just a label.<\/strong> If a hostname isn\u2019t named in any block, nginx won\u2019t error \u2014 it\u2019ll silently pick a default and serve someone else\u2019s content.<\/li>\n<li><strong><code>nginx -t<\/code> passing means <em>valid syntax<\/em>. It does not mean <em>what you intended<\/em>.<\/strong> All three configs in this story passed <code>nginx -t<\/code> with no warnings while half-broken.<\/li>\n<li><strong>Mixing <code>listen [::]:443 ssl;<\/code> (dual-stack) and <code>listen 443 ssl;<\/code> (IPv4-only) across server blocks is a footgun.<\/strong> Either go all-dual-stack or all-with-<code>ipv6only=on<\/code>. Mixing changes the listener topology in ways that affect SNI dispatch.<\/li>\n<li><strong>The <code>openssl s_client -servername X -connect Y:443<\/code> probe is a debugging superpower.<\/strong> It\u2019s a one-line command that simulates exactly what a browser does \u2014 say \u201cI want hostname X\u201d via SNI, and see which certificate the server returns. Whenever an HTTPS-served domain is misbehaving, this probe will often tell you the answer in five lines.<\/li>\n<li><strong>Pair-debugging with an AI works best when you stay in the loop.<\/strong> Treat its theories as drafts, not conclusions. Push back when you smell drift. Make it show its work.<\/li>\n<\/ol>\n<p>The whole session was somewhere between two and three hours. By the end my SEO problem was gone, my secondary domain\u2019s TLS was clean, and I had a much better mental model of how nginx makes SNI decisions across mixed-listener configurations. Worth the afternoon.<\/p>\n<hr \/>\n<p><em>Total commands run on the server during this session: about 30. Total commands run with <code>sudo<\/code>: 5. Total credentials shared with the AI: zero.<\/em><\/p>\n","protected":false},"excerpt":{"rendered":"<p>The setup that broke my Friday afternoon I was checking my own SEO. I typed \u201cquizwrap\u201d into Google. My site, QuizWrap \u2014 a free quiz-maker for students \u2014 showed up as the very first result. Great. I clicked it. A Ho\u2019oponopono prayer counter loaded. That\u2019s a completely different app I run on the same server, [&hellip;]<\/p>\n","protected":false},"author":4,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[84,27,23],"tags":[],"class_list":["post-810","post","type-post","status-publish","format-standard","hentry","category-ai","category-linux-os","category-programming"],"_links":{"self":[{"href":"https:\/\/www.cyberaka.com\/index.php?rest_route=\/wp\/v2\/posts\/810","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.cyberaka.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.cyberaka.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.cyberaka.com\/index.php?rest_route=\/wp\/v2\/users\/4"}],"replies":[{"embeddable":true,"href":"https:\/\/www.cyberaka.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=810"}],"version-history":[{"count":1,"href":"https:\/\/www.cyberaka.com\/index.php?rest_route=\/wp\/v2\/posts\/810\/revisions"}],"predecessor-version":[{"id":811,"href":"https:\/\/www.cyberaka.com\/index.php?rest_route=\/wp\/v2\/posts\/810\/revisions\/811"}],"wp:attachment":[{"href":"https:\/\/www.cyberaka.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=810"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.cyberaka.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=810"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.cyberaka.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=810"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}