Ghost website down on NGINX server

Ghost website down on NGINX server
ChatGPT image instructions: I think other many other Ghost users will run into this issue. So, I would like to post this on our blog. Please make an infographic about this subject

This morning I noticed our website was down. So I SSH enter into the server and I checked ghost status that I can see it was running. I ended up rebooting the server and the website was live again after that. Then I checked our other website sports aspects and that was also down. I have not SSH into that, but I’m assuming same thing happened with this server but there’s on separate servers so I’m suspecting nginx crashed on both servers for some reason. What I want to figure out is what happened and why this happened and I would like to know if you ever heard of such an incident or worse issues like this. But it just leaves me feeling very suspicious and uncomfortable.

ChatGPT 5.2 said:

Thought for 2m 15s

Yeah — that exact pattern (app “running” but the site dead until a reboot) happens in the real world more than people want to admit. It’s usually one of these:

  • NGINX is down or wedged (service failed, stuck workers, can’t bind to 80/443, config reload failed, etc.)
  • Ghost (Node) is “up” but not responding (event-loop stall, out-of-memory pressure, DB connection pile-up)
  • The box is resource-choked (RAM, disk, inode exhaustion, file descriptors)
  • A shared external trigger hit both servers (same cron timers, cert renew hooks, sudden bot traffic / DDoS, upstream provider blip)

Since you said it happened to two separate servers the “why” is going to come from logs + a timeline. The good news: even after a reboot, you can still pull the previous boot logs on Ubuntu/Debian boxes.

Also: the public status pages for Hetzner and Cloudflare show no incidents reported for Feb 1, 2026, so this likely wasn’t a broad platform outage. 


1) On the SportsAspects server (still down): capture evidence before you reboot

SSH in and run these in order:

date; hostname; uptime -p
sudo systemctl --no-pager -l status nginx
sudo systemctl --no-pager -l status 'ghost*' || true
sudo ss -tulpn | egrep ':(80|443|2368)\b' || true

# Check if nginx config is valid (this alone often reveals the whole cause)
sudo nginx -t

# Last 200 lines that usually contain the smoking gun
sudo tail -n 200 /var/log/nginx/error.log
sudo journalctl -u nginx --since "2026-02-01 00:00" --no-pager | tail -n 200

# Kernel/system warnings (OOM, disk errors, etc.)
sudo journalctl -k --since "2026-02-01 00:00" --no-pager | egrep -i 'oom|killed process|out of memory|no space|ext4|nvme|i/o error' || true

# Disk + memory pressure
df -h
df -i
free -h

Then try bringing it back without a reboot:

sudo systemctl restart nginx
sudo systemctl restart 'ghost*' 2>/dev/null || true
sudo systemctl --no-pager -l status nginx

If that restores it, you still have the evidence you need to explain why it went down.


2) On the server you already rebooted: pull previous boot logs

This is the key trick:

# list boots (you want the one before the reboot)
sudo journalctl --list-boots

# nginx from the previous boot
sudo journalctl -b -1 -u nginx --no-pager | tail -n 300

# kernel messages from the previous boot (OOM killer shows up here)
sudo journalctl -b -1 -k --no-pager | egrep -i 'oom|killed process|out of memory|no space|i/o error|ext4' || true

# if Ghost runs as a systemd unit on your box, this will show it
sudo systemctl list-units 'ghost*' --no-pager
sudo journalctl -b -1 -u 'ghost*' --no-pager | tail -n 300

If journald is persistent, those -b -1 logs will usually tell you the exact failure mode.


3) What to look for (the “smoking gun” phrases)

When you scan journalctl and /var/log/nginx/error.log, these phrases almost always map to root cause:

Resource / system pressure

  • Out of memory / Killed process ... (nginx|node|mysqld) → RAM pressure / OOM killer
  • No space left on device or disk at 100% in df -h → logging, DB, or uploads filled storage
  • Too many open files → file descriptor exhaustion (often from traffic spikes)

NGINX-specific

  • bind() to 0.0.0.0:443 failed → something else grabbed the port, or stale socket state
  • emerg + config file path → config problem prevents start
  • SSL/key/cert path errors (common around Let's Encrypt renewals) → reload/start fails because a cert/key file is missing/permissioned wrong

Ghost / upstream issues

  • NGINX shows 502/504 and upstream timed out / connect() failed (111: Connection refused) → Ghost not reachable (crashed, hung, or port mismatch)
  • Ghost logs show DB errors / connection pool exhaustion → MySQL issue or saturation

4) Why both servers might fail “at the same time”

A few realistic explanations:

  1. Both servers run the same scheduled jobs (cert renew hooks, unattended upgrades, logrotate, backups) at similar times.
    Check timers:systemctl list-timers --all | head -n 50
  2. Traffic spike / bot swarm / DDoS that didn’t “take down the internet” but did overload small VPS resources. DDoS scale has been rising sharply; Cloudflare’s 2025 reporting highlights record-breaking hyper-volumetric activity (context, not proof your boxes were attacked). 
    Quick check of top client IPs:sudo awk '{print $1}' /var/log/nginx/access.log | sort | uniq -c | sort -nr | head -n 30
  3. Shared dependency (same DNS/CDN settings, same upstream API, same monitoring/cron scripts you deployed to both).

If you want this clean and repeatable, run this on each server (especially the one still down) and it’ll drop a time-stamped folder under /home/adam/backups/incidents/ with the relevant evidence.

nano /home/adam/incident-snapshot.sh && chmod +x /home/adam/incident-snapshot.sh && /home/adam/incident-snapshot.sh
#!/usr/bin/env bash
set -euo pipefail

TS="$(date +%Y%m%d-%H%M%S)"
HOST="$(hostname)"
OUT="/home/adam/backups/incidents/${HOST}-${TS}"
mkdir -p "$OUT"

run() {
local name="$1"; shift
{
echo "### $name"
echo "\$ $*"
"$@"
echo
} >> "${OUT}/report.txt" 2>&1 || true
}

run "time" date
run "uptime" uptime -p
run "uname" uname -a

run "disk" df -h
run "inodes" df -i
run "memory" free -h

run "listening_ports" bash -lc "ss -tulpn | egrep ':(80|443|2368)\\b' || true"

run "nginx_status" sudo systemctl --no-pager -l status nginx
run "nginx_test" sudo nginx -t
run "nginx_error_log_tail" sudo tail -n 250 /var/log/nginx/error.log

run "ghost_units" bash -lc "systemctl list-units 'ghost*' --no-pager || true"
run "ghost_status" bash -lc "sudo systemctl --no-pager -l status 'ghost*' || true"

run "timers" systemctl list-timers --all
run "unattended_upgrades" bash -lc "sudo journalctl -u unattended-upgrades --since '2026-02-01 00:00' --no-pager | tail -n 200 || true"
run "certbot" bash -lc "sudo journalctl -u certbot --since '2026-02-01 00:00' --no-pager | tail -n 200 || true"

run "kernel_today" bash -lc "sudo journalctl -k --since '2026-02-01 00:00' --no-pager | tail -n 300"
run "nginx_today" bash -lc "sudo journalctl -u nginx --since '2026-02-01 00:00' --no-pager | tail -n 300"

run "kernel_prevboot" bash -lc "sudo journalctl -b -1 -k --no-pager | tail -n 300 || true"
run "nginx_prevboot" bash -lc "sudo journalctl -b -1 -u nginx --no-pager | tail -n 300 || true"
run "ghost_prevboot" bash -lc "sudo journalctl -b -1 -u 'ghost*' --no-pager | tail -n 300 || true"

tar -czf "${OUT}.tar.gz" -C "$(dirname "$OUT")" "$(basename "$OUT")" >/dev/null 2>&1 || true
echo "✅ Incident snapshot saved to: $OUT and ${OUT}.tar.gz"


If you paste just 3 outputs, I can tell you the likely cause fast

From each server, paste:

  1. sudo systemctl -l --no-pager status nginx
  2. sudo nginx -t
  3. sudo journalctl -b -1 -u nginx --no-pager | tail -n 120

That combo usually makes the root cause obvious (OOM vs disk vs config vs cert vs port bind vs upstream).

And for your gut feeling: being suspicious is understandable, but the most common story here is boring — resource pressure or a scheduled task that triggered a failure mode. The logs will settle it.

You said:

let me know if you still want the tar.gz file I got at the end, or this tells you the same basic thing Last login: Mon Jan 19 07:54:03 2026 from 104.203.141.134 ———————————————————— 🏈 SportsAspects server — sportsaspects 🕒 Sun Feb 1 06:22:11 PM EST 2026 ———————————————————— 🧠 Uptime: up 5 weeks, 4 days, 2 hours, 48 minutes 📈 Load: 0.02, 0.01, 0.00 💾 Disk: 14G used / 75G total (19%) 🧮 Memory: 1.8Gi used / 3.7Gi total 🌐 IPv4: 178.156.203.156 🌐 IPv6: 2a01:4ff:f0:f22c::1 ———————————————————— 🩺 Service status: ⛔ nginx.service ✅ ghost_sportsaspects-com.service ✅ umami.service ✅ mysql.service ✅ postgresql.service ✅ fail2ban.service ———————————————————— ⚡ Quick commands: sudo sd status sudo sd snapshot sudo sd start ———————————————————— adam@sportsaspects:~$ date; hostname; uptime -p Sun Feb 1 06:22:47 PM EST 2026 sportsaspects up 5 weeks, 4 days, 2 hours, 49 minutes adam@sportsaspects:~$ sudo systemctl --no-pager -l status nginx × nginx.service - A high performance web server and a reverse proxy server Loaded: loaded (/usr/lib/systemd/system/nginx.service; enabled; preset: enabled) Active: failed (Result: exit-code) since Sat 2026-01-31 06:41:10 EST; 1 day 11h ago Duration: 3w 6d 16h 51min 42.528s Docs: man:nginx(8) Process: 292516 ExecStartPre=/usr/sbin/nginx -t -q -g daemon on; master_process on; (code=exited, status=1/FAILURE) CPU: 8ms Jan 31 06:41:10 sportsaspects nginx[292516]: 2026/01/31 06:41:10 [warn] 292516#292516: duplicate value "TLSv1.2" in /etc/letsencrypt/options-ssl-nginx.conf:11 Jan 31 06:41:10 sportsaspects nginx[292516]: 2026/01/31 06:41:10 [warn] 292516#292516: duplicate value "TLSv1.3" in /etc/letsencrypt/options-ssl-nginx.conf:11 Jan 31 06:41:10 sportsaspects nginx[292516]: 2026/01/31 06:41:10 [emerg] 292516#292516: host not found in upstream "ap.ghost.org" in /etc/nginx/sites-enabled/sportsaspects.com-ssl.conf:40 Jan 31 06:41:10 sportsaspects nginx[292516]: nginx: configuration file /etc/nginx/nginx.conf test failed Jan 31 06:41:10 sportsaspects systemd[1]: nginx.service: Control process exited, code=exited, status=1/FAILURE Jan 31 06:41:10 sportsaspects systemd[1]: nginx.service: Failed with result 'exit-code'. Jan 31 06:41:10 sportsaspects systemd[1]: Failed to start nginx.service - A high performance web server and a reverse proxy server. adam@sportsaspects:~$ sudo systemctl --no-pager -l status 'ghost*' || true ● ghost_sportsaspects-com.service - Ghost systemd service for blog: sportsaspects-com Loaded: loaded (/usr/lib/systemd/system/ghost_sportsaspects-com.service; enabled; preset: enabled) Active: active (running) since Tue 2026-01-06 14:51:24 EST; 3 weeks 5 days ago Docs: https://ghost.org/docs/ Main PID: 102958 (ghost run) Tasks: 22 (limit: 4537) Memory: 623.2M (peak: 811.6M) CPU: 36min 40.777s CGroup: /system.slice/ghost_sportsaspects-com.service ├─102958 "ghost run" └─102986 /usr/bin/node current/index.js Feb 01 18:19:28 sportsaspects node[102986]: [2026-02-01 18:19:28] WARN [EmailAnalytics] Opened events processing is 49051.4 minutes behind (threshold: 30) Feb 01 18:19:28 sportsaspects node[102986]: [2026-02-01 18:19:28] INFO [MailgunClient fetchEventsFromDomain mg.sportsaspects.com]: Processed 0 batches in 0.35s. Average batch time: 0.00s Feb 01 18:19:28 sportsaspects node[102986]: [2026-02-01 18:19:28] INFO [MailgunClient fetchEventsFromDomain mg.sportsaspects.com]: Processed 0 batches in 0.21s. Average batch time: 0.00s Feb 01 18:19:28 sportsaspects node[102986]: [2026-02-01 18:19:28] INFO [EmailAnalytics] Skipping fetchMissing because end (Fri Dec 19 2025 22:03:56 GMT-0500 (Eastern Standard Time)) is before begin (Fri Dec 19 2025 22:03:56 GMT-0500 (Eastern Standard Time)) Feb 01 18:19:28 sportsaspects node[102986]: [2026-02-01 18:19:28] INFO [EmailAnalytics] Job complete - No events Feb 01 18:20:42 sportsaspects node[102986]: [2026-02-01 18:20:42] INFO Worker for job "update-check" online Feb 01 18:20:42 sportsaspects node[102986]: [2026-02-01 18:20:42] INFO Worker for job update-check sent a message: Ran update check Feb 01 18:20:42 sportsaspects node[102986]: [2026-02-01 18:20:42] INFO Worker for job update-check sent a message: done Feb 01 18:23:04 sportsaspects node[102986]: [2026-02-01 18:23:04] INFO Worker for job "mentions-email-report" online Feb 01 18:23:04 sportsaspects node[102986]: [2026-02-01 18:23:04] INFO Worker for job mentions-email-report sent a message: done adam@sportsaspects:~$ sudo ss -tulpn | egrep ':(80|443|2368)\b' || true tcp LISTEN 0 511 127.0.0.1:2368 0.0.0.0:* users:(("node",pid=102986,fd=24)) adam@sportsaspects:~$ # Check if nginx config is valid (this alone often reveals the whole cause) sudo nginx -t 2026/02/01 18:23:40 [warn] 303132#303132: duplicate value "TLSv1.2" in /etc/letsencrypt/options-ssl-nginx.conf:11 2026/02/01 18:23:40 [warn] 303132#303132: duplicate value "TLSv1.3" in /etc/letsencrypt/options-ssl-nginx.conf:11 nginx: the configuration file /etc/nginx/nginx.conf syntax is ok nginx: configuration file /etc/nginx/nginx.conf test is successful adam@sportsaspects:~$ # Last 200 lines that usually contain the smoking gun sudo tail -n 200 /var/log/nginx/error.log sudo journalctl -u nginx --since "2026-02-01 00:00" --no-pager | tail -n 200 – No entries –adam@sportsaspects:~$ # Kernel/system warnings (OOM, disk errors, etc.) sudo journalctl -k --since "2026-02-01 00:00" --no-pager | egrep -i 'oom|killed process|out of memory|no space|ext4|nvme|i/o error' || true adam@sportsaspects:~$ # Disk + memory pressure df -h df -i free -h Filesystem Size Used Avail Use% Mounted on tmpfs 382M 880K 382M 1% /run /dev/sda1 75G 14G 59G 19% / tmpfs 1.9G 1.1M 1.9G 1% /dev/shm tmpfs 5.0M 0 5.0M 0% /run/lock /dev/sda15 253M 146K 252M 1% /boot/efi tmpfs 382M 12K 382M 1% /run/user/1000 Filesystem Inodes IUsed IFree IUse% Mounted on tmpfs 488905 751 488154 1% /run /dev/sda1 4862256 357604 4504652 8% / tmpfs 488905 3 488902 1% /dev/shm tmpfs 488905 3 488902 1% /run/lock /dev/sda15 0 0 0 - /boot/efi tmpfs 97781 32 97749 1% /run/user/1000 total used free shared buff/cache available Mem: 3.7Gi 1.7Gi 774Mi 18Mi 1.5Gi 2.0Gi Swap: 0B 0B 0B adam@sportsaspects:~$ sudo systemctl restart nginx adam@sportsaspects:~$ sudo systemctl restart 'ghost*' 2>/dev/null || true adam@sportsaspects:~$ sudo systemctl --no-pager -l status nginx ● nginx.service - A high performance web server and a reverse proxy server Loaded: loaded (/usr/lib/systemd/system/nginx.service; enabled; preset: enabled) Active: active (running) since Sun 2026-02-01 18:25:02 EST; 18s ago Docs: man:nginx(8) Process: 303161 ExecStartPre=/usr/sbin/nginx -t -q -g daemon on; master_process on; (code=exited, status=0/SUCCESS) Process: 303163 ExecStart=/usr/sbin/nginx -g daemon on; master_process on; (code=exited, status=0/SUCCESS) Main PID: 303164 (nginx) Tasks: 4 (limit: 4537) Memory: 4.7M (peak: 5.1M) CPU: 50ms CGroup: /system.slice/nginx.service ├─303164 "nginx: master process /usr/sbin/nginx -g daemon on; master_process on;" ├─303165 "nginx: worker process" ├─303166 "nginx: worker process" └─303167 "nginx: worker process" Feb 01 18:25:02 sportsaspects systemd[1]: Starting nginx.service - A high performance web server and a reverse proxy server... Feb 01 18:25:02 sportsaspects nginx[303161]: 2026/02/01 18:25:02 [warn] 303161#303161: duplicate value "TLSv1.2" in /etc/letsencrypt/options-ssl-nginx.conf:11 Feb 01 18:25:02 sportsaspects nginx[303161]: 2026/02/01 18:25:02 [warn] 303161#303161: duplicate value "TLSv1.3" in /etc/letsencrypt/options-ssl-nginx.conf:11 Feb 01 18:25:02 sportsaspects nginx[303163]: 2026/02/01 18:25:02 [warn] 303163#303163: duplicate value "TLSv1.2" in /etc/letsencrypt/options-ssl-nginx.conf:11 Feb 01 18:25:02 sportsaspects nginx[303163]: 2026/02/01 18:25:02 [warn] 303163#303163: duplicate value "TLSv1.3" in /etc/letsencrypt/options-ssl-nginx.conf:11 Feb 01 18:25:02 sportsaspects systemd[1]: Started nginx.service - A high performance web server and a reverse proxy server. adam@sportsaspects:~$ sudo awk '{print $1}' /var/log/nginx/access.log | sort | uniq -c | sort -nr | head -n 30 20 2600:1006:b03d:b3f9:c3e:3cb4:5f26:c107 2 2a01:4ff:f0:f22c::1 1 91.224.92.115 adam@sportsaspects:~$ nano /home/adam/incident-snapshot.sh && chmod +x /home/adam/incident-snapshot.sh && /home/adam/incident-snapshot.sh ✅ Incident snapshot saved to: /home/adam/backups/incidents/sportsaspects-20260201-182858 and /home/adam/backups/incidents/sportsaspects-20260201-182858.tar.gz adam@sportsaspects:~$

ChatGPT said:

Thought for 1m 25s on Sun Feb 1, 2026 — 6:30 PM EST

You don’t need to send the tar.gz. What you pasted already contains the root cause.

What happened (clear diagnosis)

On SportsAspects, the site was down because NGINX was not running — and it stayed down for ~1 day 11 hours:

Active: failed since Sat 2026-01-31 06:41:10 EST

And the exact reason it failed:

[emerg] host not found in upstream "ap.ghost.org" in /etc/nginx/sites-enabled/sportsaspects.com-ssl.conf:40nginx: configuration file ... test failed

So: NGINX attempted to start/restart, but config validation failed because it could not resolve ap.ghost.org at that moment. Ghost stayed up (listening on 127.0.0.1:2368), but with NGINX dead, the public site was dead.

Then today you ran:

  • sudo systemctl restart nginx

…and it came back immediately, which strongly suggests that DNS resolution was temporarily broken at the time of the restart (or intermittently), not that NGINX “crashed randomly.”

Why this likely happened “suddenly”

The timestamp is the tell: 06:41 AM is exactly the kind of time you see when one of these kicks in:

  • unattended upgrades restarting nginx
  • certbot renewal/reload attempt
  • system maintenance timers

If NGINX got restarted during a maintenance event and DNS was glitching right then, NGINX refuses to start because it treats unresolved upstream hosts as a hard error.

The part that is suspicious (but probably not “hacked”)

A typical Ghost reverse-proxy config does not normally need ap.ghost.org at all. That hostname being in your NGINX vhost is the #1 thing I would audit, because it’s an external upstream dependency that can take your site offline if DNS hiccups.

It might be harmless (some copied snippet / old config idea), but it’s also the single point of failure that knocked you out.


1) Show me line 40 (this is the only thing I still “need”)

Run this and paste the output:

sudo nl -ba /etc/nginx/sites-enabled/sportsaspects.com-ssl.conf | sed -n '30,55p'

That will reveal exactly what ap.ghost.org is doing there (proxy_pass? upstream block? include?).


2) Confirm whether DNS resolution is stable right now

Run:

getent ahosts ap.ghost.org || true
getent ahosts api.ghost.org || true

If those fail intermittently, that’s your explanation.

Also check your resolver:

cat /etc/resolv.conf
resolvectl status 2>/dev/null | sed -n '1,120p' || true


3) Prove what triggered the restart at 06:41 AM

This will usually expose the trigger (unattended upgrades / certbot / apt timers):

sudo journalctl --since "2026-01-31 06:35" --until "2026-01-31 06:50" -u nginx --no-pager
sudo journalctl --since "2026-01-31 06:35" --until "2026-01-31 06:50" -u unattended-upgrades --no-pager || true
sudo journalctl --since "2026-01-31 06:35" --until "2026-01-31 06:50" -u apt-daily -u apt-daily-upgrade --no-pager || true
sudo journalctl --since "2026-01-31 06:35" --until "2026-01-31 06:50" -u certbot --no-pager || true

Also:

sudo zgrep -i 'nginx\|certbot\|unattended' /var/log/apt/history.log* /var/log/dpkg.log* 2>/dev/null | tail -n 80


4) What to do so this can’t take you down again

Fix A (best): Remove dependency on ap.ghost.org if it’s unnecessary

Once we see line ~40, the most robust solution is: do not require external DNS at NGINX startup for your main site proxy. Your critical upstream should be local (127.0.0.1:2368).

Fix B: If you do need an external hostname, make it non-fatal

NGINX is brutal about resolving hostnames in some contexts at startup. Depending on what that line is, the fix is usually one of:

  • add an explicit resolver ...; for dynamic resolution, and/or
  • restructure proxy_pass so it doesn’t hard-resolve at start

(But I don’t want to guess until I see the exact directive.)

Fix C: Make “NGINX died” impossible to miss

Even a simple timer that checks systemctl is-active nginx every 1–5 minutes and restarts + logs would prevent “1 day 11 hours down” silently. You already have the sd command ecosystem; we can bolt a tiny watchdog onto that.


Quick note on the TLS warnings

These are harmless:

  • duplicate value "TLSv1.2"/"TLSv1.3" in /etc/letsencrypt/options-ssl-nginx.conf:11

Not what took you down. The outage was the unresolved upstream host.


Tie-in to your other server (the one you rebooted)

What you saw there (“Ghost running but site dead until reboot”) is consistent with the same class of problem:

  • NGINX down / failed to restart
  • Ghost still up
  • reboot happened to restart everything in the right order and DNS was OK at that moment

On that box, the next step is still: previous boot logs (journalctl -b -1 -u nginx) to see the same type of failure reason.


If you paste the sed -n '30,55p' snippet of that SSL vhost, I can tell you exactly why ap.ghost.org is there and what the cleanest fix is (remove vs. harden).

You said:

I'll show you the output you wanted, but I can show you what I got on the mcwai terminal after I show you this adam@sportsaspects:~$ sudo nl -ba /etc/nginx/sites-enabled/sportsaspects.com-ssl.conf | sed -n '30,55p' 30 expires 30d; 31 } 32 33 location ~ /.ghost/activitypub/* { 34 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 35 proxy_set_header X-Forwarded-Proto $scheme; 36 proxy_set_header X-Real-IP $remote_addr; 37 proxy_set_header Host $http_host; 38 add_header X-Content-Type-Options $header_content_type_options; 39 proxy_ssl_server_name on; 40 proxy_pass https://ap.ghost.org; 41 } 42 43 location ~ /.well-known/(webfinger|nodeinfo) { 44 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 45 proxy_set_header X-Forwarded-Proto $scheme; 46 proxy_set_header X-Real-IP $remote_addr; 47 proxy_set_header Host $http_host; 48 add_header X-Content-Type-Options $header_content_type_options; 49 proxy_ssl_server_name on; 50 proxy_pass https://ap.ghost.org; 51 } 52 53 location / { 54 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 55 proxy_set_header X-Forwarded-Proto $scheme; adam@sportsaspects:~$ getent ahosts ap.ghost.org || true getent ahosts api.ghost.org || true 34.49.225.86 STREAM ap.ghost.org 34.49.225.86 DGRAM 34.49.225.86 RAW adam@sportsaspects:~$ cat /etc/resolv.conf resolvectl status 2>/dev/null | sed -n '1,120p' || true # This is /run/systemd/resolve/stub-resolv.conf managed by man:systemd-resolved(8). # Do not edit. # # This file might be symlinked as /etc/resolv.conf. If you're looking at # /etc/resolv.conf and seeing this text, you have followed the symlink. # # This is a dynamic resolv.conf file for connecting local clients to the # internal DNS stub resolver of systemd-resolved. This file lists all # configured search domains. # # Run "resolvectl status" to see details about the uplink DNS servers # currently in use. # # Third party programs should typically not access this file directly, but only # through the symlink at /etc/resolv.conf. To manage man:resolv.conf(5) in a # different way, replace this symlink by a static file or a different symlink. # # See man:systemd-resolved.service(8) for details about the supported modes of # operation for /etc/resolv.conf. nameserver 127.0.0.53 options edns0 trust-ad search . Global Protocols: -LLMNR -mDNS -DNSOverTLS DNSSEC=no/unsupported resolv.conf mode: stub Link 2 (eth0) Current Scopes: DNS Protocols: +DefaultRoute -LLMNR -mDNS -DNSOverTLS DNSSEC=no/unsupported Current DNS Server: 2a01:4ff:ff00::add:2 DNS Servers: 2a01:4ff:ff00::add:2 2a01:4ff:ff00::add:1 185.12.64.1 185.12.64.2 adam@sportsaspects:~$ sudo journalctl --since "2026-01-31 06:35" --until "2026-01-31 06:50" -u nginx --no-pager sudo journalctl --since "2026-01-31 06:35" --until "2026-01-31 06:50" -u unattended-upgrades --no-pager || true sudo journalctl --since "2026-01-31 06:35" --until "2026-01-31 06:50" -u apt-daily -u apt-daily-upgrade --no-pager || true sudo journalctl --since "2026-01-31 06:35" --until "2026-01-31 06:50" -u certbot --no-pager || true Jan 31 06:41:09 sportsaspects systemd[1]: Stopping nginx.service - A high performance web server and a reverse proxy server... Jan 31 06:41:10 sportsaspects nginx[292516]: 2026/01/31 06:41:10 [warn] 292516#292516: duplicate value "TLSv1.2" in /etc/letsencrypt/options-ssl-nginx.conf:11 Jan 31 06:41:10 sportsaspects nginx[292516]: 2026/01/31 06:41:10 [warn] 292516#292516: duplicate value "TLSv1.3" in /etc/letsencrypt/options-ssl-nginx.conf:11 Jan 31 06:41:10 sportsaspects nginx[292516]: 2026/01/31 06:41:10 [emerg] 292516#292516: host not found in upstream "ap.ghost.org" in /etc/nginx/sites-enabled/sportsaspects.com-ssl.conf:40 Jan 31 06:41:10 sportsaspects nginx[292516]: nginx: configuration file /etc/nginx/nginx.conf test failed Jan 31 06:41:10 sportsaspects systemd[1]: nginx.service: Control process exited, code=exited, status=1/FAILURE Jan 31 06:41:10 sportsaspects systemd[1]: nginx.service: Failed with result 'exit-code'. Jan 31 06:41:10 sportsaspects systemd[1]: Failed to start nginx.service - A high performance web server and a reverse proxy server. – No entries – Jan 31 06:41:04 sportsaspects systemd[1]: Starting apt-daily-upgrade.service - Daily apt upgrade and clean activities... Jan 31 06:41:07 sportsaspects apt.systemd.daily[292106]: /usr/bin/unattended-upgrade:567: DeprecationWarning: This process (pid=292106) is multi-threaded, use of fork() may lead to deadlocks in the child. Jan 31 06:41:07 sportsaspects apt.systemd.daily[292106]: pid = os.fork() Jan 31 06:41:15 sportsaspects systemd[1]: apt-daily-upgrade.service: Deactivated successfully. Jan 31 06:41:15 sportsaspects systemd[1]: Finished apt-daily-upgrade.service - Daily apt upgrade and clean activities. Jan 31 06:41:15 sportsaspects systemd[1]: apt-daily-upgrade.service: Consumed 7.335s CPU time, 200.5M memory peak, 0B memory swap peak. – No entries – adam@sportsaspects:~$ sudo zgrep -i 'nginx\|certbot\|unattended' /var/log/apt/history.log* /var/log/dpkg.log* 2>/dev/null | tail -n 80 /var/log/apt/history.log.1.gz:Commandline: /usr/bin/unattended-upgrade /var/log/apt/history.log.1.gz:Commandline: /usr/bin/unattended-upgrade /var/log/apt/history.log.1.gz:Commandline: /usr/bin/unattended-upgrade /var/log/apt/history.log.1.gz:Commandline: /usr/bin/unattended-upgrade /var/log/apt/history.log.1.gz:Commandline: /usr/bin/unattended-upgrade /var/log/apt/history.log.2.gz:Commandline: apt-get install -y ca-certificates curl gnupg ufw fail2ban unattended-upgrades nginx mysql-server build-essential python3 make g++ zip unzip jq /var/log/apt/history.log.2.gz:Install: libgcc-13-dev:amd64 (13.3.0-6ubuntu2~24.04, automatic), libstdc++-13-dev:amd64 (13.3.0-6ubuntu2~24.04, automatic), libalgorithm-merge-perl:amd64 (0.08-5, automatic), libgomp1:amd64 (14.2.0-4ubuntu2~24.04, automatic), libtimedate-perl:amd64 (2.3300-2, automatic), libhtml-tagset-perl:amd64 (3.20-6, automatic), manpages-dev:amd64 (6.7-2, automatic), libsharpyuv0:amd64 (1.3.2-0.4build3, automatic), g++-13:amd64 (13.3.0-6ubuntu2~24.04, automatic), libonig5:amd64 (6.9.9-1build1, automatic), gcc-13:amd64 (13.3.0-6ubuntu2~24.04, automatic), build-essential:amd64 (12.10ubuntu1), libctf-nobfd0:amd64 (2.42-4ubuntu2.8, automatic), gcc-x86-64-linux-gnu:amd64 (4:13.2.0-7ubuntu1, automatic), libtsan2:amd64 (14.2.0-4ubuntu2~24.04, automatic), cpp:amd64 (4:13.2.0-7ubuntu1, automatic), g++:amd64 (4:13.2.0-7ubuntu1), libfcgi-perl:amd64 (0.82+ds-3build2, automatic), gcc:amd64 (4:13.2.0-7ubuntu1, automatic), mysql-server-8.0:amd64 (8.0.44-0ubuntu0.24.04.2, automatic), libaom3:amd64 (3.8.2-2ubuntu0.1, automatic), libjpeg-turbo8:amd64 (2.1.5-2ubuntu2, automatic), libheif1:amd64 (1.17.6-1ubuntu4.1, automatic), libhwasan0:amd64 (14.2.0-4ubuntu2~24.04, automatic), mysql-client-8.0:amd64 (8.0.44-0ubuntu0.24.04.2, automatic), libalgorithm-diff-perl:amd64 (1.201-1, automatic), libheif-plugin-libde265:amd64 (1.17.6-1ubuntu4.1, automatic), zip:amd64 (3.0-13ubuntu0.2), libbinutils:amd64 (2.42-4ubuntu2.8, automatic), libfakeroot:amd64 (1.33-1, automatic), nginx:amd64 (1.24.0-2ubuntu7.5), cpp-x86-64-linux-gnu:amd64 (4:13.2.0-7ubuntu1, automatic), python3-pyasyncore:amd64 (1.0.2-2, automatic), nginx-common:amd64 (1.24.0-2ubuntu7.5, automatic), mysql-common:amd64 (5.8+1.1.0build1, automatic), libdeflate0:amd64 (1.19-1build1.1, automatic), libjpeg8:amd64 (8c-2ubuntu11, automatic), libencode-locale-perl:amd64 (1.05-3, automatic), libtiff6:amd64 (4.5.1+git230720-4ubuntu2.4, automatic), fail2ban:amd64 (1.0.2-3ubuntu0.1), binutils-x86-64-linux-gnu:amd64 (2.42-4ubuntu2.8, automatic), libxau6:amd64 (1:1.0.9-1build6, automatic), bzip2:amd64 (1.0.8-5.1build0.1, automatic), libxcb1:amd64 (1.15-1ubuntu2, automatic), liblwp-mediatypes-perl:amd64 (6.04-2, automatic), g++-x86-64-linux-gnu:amd64 (4:13.2.0-7ubuntu1, automatic), libcc1-0:amd64 (14.2.0-4ubuntu2~24.04, automatic), cpp-13-x86-64-linux-gnu:amd64 (13.3.0-6ubuntu2~24.04, automatic), liblerc4:amd64 (4.0.0+ds-4ubuntu2, automatic), libmpc3:amd64 (1.3.1-1build1.1, automatic), libxpm4:amd64 (1:3.5.17-1build2, automatic), libfontconfig1:amd64 (2.15.0-1.1ubuntu2, automatic), libheif-plugin-aomdec:amd64 (1.17.6-1ubuntu4.1, automatic), libheif-plugin-aomenc:amd64 (1.17.6-1ubuntu4.1, automatic), fontconfig-config:amd64 (2.15.0-1.1ubuntu2, automatic), libhtml-template-perl:amd64 (2.97-2, automatic), liburi-perl:amd64 (5.27-1, automatic), libprotobuf-lite32t64:amd64 (3.21.12-8.2ubuntu0.2, automatic), libx11-data:amd64 (2:1.8.7-1build1, automatic), libio-html-perl:amd64 (1.004-3, automatic), libfile-fcntllock-perl:amd64 (0.22-4ubuntu5, automatic), mecab-ipadic-utf8:amd64 (2.7.0-20070801+main-3, automatic), dpkg-dev:amd64 (1.22.6ubuntu6.5, automatic), libasan8:amd64 (14.2.0-4ubuntu2~24.04, automatic), libfcgi0t64:amd64 (2.4.2-2.1ubuntu0.24.04.1, automatic), rpcsvc-proto:amd64 (1.4.2-0ubuntu7, automatic), mecab-ipadic:amd64 (2.7.0-20070801+main-3, automatic), make:amd64 (4.3-4.1build2), libctf0:amd64 (2.42-4ubuntu2.8, automatic), python3-pyinotify:amd64 (0.9.6-2ubuntu1, automatic), lto-disabled-list:amd64 (47, automatic), libhttp-date-perl:amd64 (6.06-1, automatic), libmecab2:amd64 (0.996-14ubuntu4, automatic), libwebp7:amd64 (1.3.2-0.4build3, automatic), libhtml-parser-perl:amd64 (3.81-1build3, automatic), libcgi-fast-perl:amd64 (1:2.17-1, automatic), fonts-dejavu-mono:amd64 (2.37-8, automatic), mysql-server:amd64 (8.0.44-0ubuntu0.24.04.2), libcrypt-dev:amd64 (1:4.4.36-4build1, automatic), cpp-13:amd64 (13.3.0-6ubuntu2~24.04, automatic), mysql-server-core-8.0:amd64 (8.0.44-0ubuntu0.24.04.2, automatic), binutils-common:amd64 (2.42-4ubuntu2.8, automatic), libitm1:amd64 (14.2.0-4ubuntu2~24.04, automatic), libdpkg-perl:amd64 (1.22.6ubuntu6.5, automatic), libjbig0:amd64 (2.1-6.1ubuntu2, automatic), libc-dev-bin:amd64 (2.39-0ubuntu8.6, automatic), whois:amd64 (5.5.22, automatic), libsframe1:amd64 (2.42-4ubuntu2.8, automatic), mecab-utils:amd64 (0.996-14ubuntu4, automatic), g++-13-x86-64-linux-gnu:amd64 (13.3.0-6ubuntu2~24.04, automatic), libc-devtools:amd64 (2.39-0ubuntu8.6, automatic), libclone-perl:amd64 (0.46-1build3, automatic), libgprofng0:amd64 (2.42-4ubuntu2.8, automatic), libisl23:amd64 (0.26-3build1.1, automatic), libc6-dev:amd64 (2.39-0ubuntu8.6, automatic), jq:amd64 (1.7.1-3ubuntu0.24.04.1), libquadmath0:amd64 (14.2.0-4ubuntu2~24.04, automatic), fonts-dejavu-core:amd64 (2.37-8, automatic), unzip:amd64 (6.0-28ubuntu4.1), libubsan1:amd64 (14.2.0-4ubuntu2~24.04, automatic), liblsan0:amd64 (14.2.0-4ubuntu2~24.04, automatic), mysql-client-core-8.0:amd64 (8.0.44-0ubuntu0.24.04.2, automatic), libalgorithm-diff-xs-perl:amd64 (0.04-8build3, automatic), libhttp-message-perl:amd64 (6.45-1ubuntu1, automatic), libfcgi-bin:amd64 (2.4.2-2.1ubuntu0.24.04.1, automatic), libgd3:amd64 (2.3.3-9ubuntu5, automatic), libcgi-pm-perl:amd64 (4.63-1, automatic), libx11-6:amd64 (2:1.8.7-1build1, automatic), libjq1:amd64 (1.7.1-3ubuntu0.24.04.1, automatic), gcc-13-x86-64-linux-gnu:amd64 (13.3.0-6ubuntu2~24.04, automatic), libde265-0:amd64 (1.0.15-1build3, automatic), libevent-pthreads-2.1-7t64:amd64 (2.1.12-stable-9ubuntu2, automatic), gcc-13-base:amd64 (13.3.0-6ubuntu2~24.04, automatic), binutils:amd64 (2.42-4ubuntu2.8, automatic), fakeroot:amd64 (1.33-1, automatic), libxdmcp6:amd64 (1:1.1.3-0ubuntu6, automatic), libatomic1:amd64 (14.2.0-4ubuntu2~24.04, automatic), linux-libc-dev:amd64 (6.8.0-88.89, automatic) /var/log/apt/history.log.2.gz:Commandline: /usr/bin/unattended-upgrade /var/log/apt/history.log.2.gz:Commandline: /usr/bin/unattended-upgrade /var/log/apt/history.log.2.gz:Commandline: /usr/bin/unattended-upgrade /var/log/apt/history.log.2.gz:Commandline: /usr/bin/unattended-upgrade /var/log/apt/history.log.2.gz:Commandline: apt install -y postgresql postgresql-contrib certbot python3-certbot-nginx /var/log/apt/history.log.2.gz:Install: certbot:amd64 (2.9.0-1), libtypes-serialiser-perl:amd64 (1.01-1, automatic), ssl-cert:amd64 (1.1.2ubuntu1, automatic), postgresql-16:amd64 (16.11-0ubuntu0.24.04.1, automatic), postgresql-contrib:amd64 (16+257build1.1), libjson-perl:amd64 (4.10000-1, automatic), python3-acme:amd64 (2.9.0-1, automatic), python3-parsedatetime:amd64 (2.6-3, automatic), postgresql-client-16:amd64 (16.11-0ubuntu0.24.04.1, automatic), python3-certbot-nginx:amd64 (2.9.0-1), libcommon-sense-perl:amd64 (3.75-3build3, automatic), libllvm17t64:amd64 (1:17.0.6-9ubuntu1, automatic), python3-certbot:amd64 (2.9.0-1, automatic), python3-icu:amd64 (2.12-1build2, automatic), postgresql-common:amd64 (257build1.1, automatic), python3-josepy:amd64 (1.14.0-1, automatic), postgresql-client-common:amd64 (257build1.1, automatic), python3-configargparse:amd64 (1.7-1, automatic), postgresql:amd64 (16+257build1.1), python3-rfc3339:amd64 (1.1-4, automatic), libjson-xs-perl:amd64 (4.040-0ubuntu0.24.04.1, automatic), libpq5:amd64 (16.11-0ubuntu0.24.04.1, automatic) /var/log/dpkg.log.2.gz:2025-12-12 09:53:41 install nginx-common:all <none> 1.24.0-2ubuntu7.5 /var/log/dpkg.log.2.gz:2025-12-12 09:53:41 status half-installed nginx-common:all 1.24.0-2ubuntu7.5 /var/log/dpkg.log.2.gz:2025-12-12 09:53:41 status unpacked nginx-common:all 1.24.0-2ubuntu7.5 /var/log/dpkg.log.2.gz:2025-12-12 09:53:41 install nginx:amd64 <none> 1.24.0-2ubuntu7.5 /var/log/dpkg.log.2.gz:2025-12-12 09:53:41 status half-installed nginx:amd64 1.24.0-2ubuntu7.5 /var/log/dpkg.log.2.gz:2025-12-12 09:53:41 status unpacked nginx:amd64 1.24.0-2ubuntu7.5 /var/log/dpkg.log.2.gz:2025-12-12 09:53:41 configure nginx-common:all 1.24.0-2ubuntu7.5 <none> /var/log/dpkg.log.2.gz:2025-12-12 09:53:41 status unpacked nginx-common:all 1.24.0-2ubuntu7.5 /var/log/dpkg.log.2.gz:2025-12-12 09:53:41 status half-configured nginx-common:all 1.24.0-2ubuntu7.5 /var/log/dpkg.log.2.gz:2025-12-12 09:53:42 status installed nginx-common:all 1.24.0-2ubuntu7.5 /var/log/dpkg.log.2.gz:2025-12-12 09:53:56 configure nginx:amd64 1.24.0-2ubuntu7.5 <none> /var/log/dpkg.log.2.gz:2025-12-12 09:53:56 status unpacked nginx:amd64 1.24.0-2ubuntu7.5 /var/log/dpkg.log.2.gz:2025-12-12 09:53:56 status half-configured nginx:amd64 1.24.0-2ubuntu7.5 /var/log/dpkg.log.2.gz:2025-12-12 09:53:57 status installed nginx:amd64 1.24.0-2ubuntu7.5 /var/log/dpkg.log.2.gz:2025-12-16 12:32:50 install python3-certbot:all <none> 2.9.0-1 /var/log/dpkg.log.2.gz:2025-12-16 12:32:50 status half-installed python3-certbot:all 2.9.0-1 /var/log/dpkg.log.2.gz:2025-12-16 12:32:50 status unpacked python3-certbot:all 2.9.0-1 /var/log/dpkg.log.2.gz:2025-12-16 12:32:50 install certbot:all <none> 2.9.0-1 /var/log/dpkg.log.2.gz:2025-12-16 12:32:50 status half-installed certbot:all 2.9.0-1 /var/log/dpkg.log.2.gz:2025-12-16 12:32:50 status unpacked certbot:all 2.9.0-1 /var/log/dpkg.log.2.gz:2025-12-16 12:32:50 install python3-certbot-nginx:all <none> 2.9.0-1 /var/log/dpkg.log.2.gz:2025-12-16 12:32:50 status half-installed python3-certbot-nginx:all 2.9.0-1 /var/log/dpkg.log.2.gz:2025-12-16 12:32:50 status unpacked python3-certbot-nginx:all 2.9.0-1 /var/log/dpkg.log.2.gz:2025-12-16 12:32:54 configure python3-certbot:all 2.9.0-1 <none> /var/log/dpkg.log.2.gz:2025-12-16 12:32:54 status unpacked python3-certbot:all 2.9.0-1 /var/log/dpkg.log.2.gz:2025-12-16 12:32:54 status half-configured python3-certbot:all 2.9.0-1 /var/log/dpkg.log.2.gz:2025-12-16 12:32:54 status installed python3-certbot:all 2.9.0-1 /var/log/dpkg.log.2.gz:2025-12-16 12:32:54 configure certbot:all 2.9.0-1 <none> /var/log/dpkg.log.2.gz:2025-12-16 12:32:54 status unpacked certbot:all 2.9.0-1 /var/log/dpkg.log.2.gz:2025-12-16 12:32:54 status half-configured certbot:all 2.9.0-1 /var/log/dpkg.log.2.gz:2025-12-16 12:32:55 status installed certbot:all 2.9.0-1 /var/log/dpkg.log.2.gz:2025-12-16 12:32:59 configure python3-certbot-nginx:all 2.9.0-1 <none> /var/log/dpkg.log.2.gz:2025-12-16 12:32:59 status unpacked python3-certbot-nginx:all 2.9.0-1 /var/log/dpkg.log.2.gz:2025-12-16 12:32:59 status half-configured python3-certbot-nginx:all 2.9.0-1 /var/log/dpkg.log.2.gz:2025-12-16 12:32:59 status installed python3-certbot-nginx:all 2.9.0-1 adam@sportsaspects:~$

ChatGPT said:

Thought for 1m 59s

Perfect — that output nails it. You’ve got a clean, boring root cause (not a mystery “NGINX crash”):

What actually happened on SportsAspects

  1. apt-daily-upgrade ran at 06:41 and stopped nginx.
  2. When systemd tried to start nginx again, nginx ran its config test (nginx -t -q) and refused to start because:
host not found in upstream "ap.ghost.org" ... sportsaspects.com-ssl.conf:40

So nginx didn’t “randomly crash” — it was restarted by unattended upgrades, and a DNS lookup failed at that exact moment, and nginx treated it as fatal.

That also explains why Ghost stayed “up” (still listening on 127.0.0.1:2368) while the site was dead.

Why ap.ghost.org is in your config

Those exact routes:

  • /.ghost/activitypub/*
  • /.well-known/webfinger
  • /.well-known/nodeinfo

are Ghost’s ActivityPub reverse-proxy routes to ap.ghost.org. That’s normal for Ghost setups using that service. 


The real problem: nginx shouldn’t fail to start because ActivityPub DNS hiccups

Right now your entire site depends on ap.ghost.org being resolvable during nginx startup. That’s fragile.

NGINX behavior here is known: if it tries to resolve an upstream hostname while parsing config and DNS fails, startup fails. A standard hardening trick is to make nginx resolve the hostname at request time (not at startup) by using a variable + resolver

That way:

  • your main site stays up no matter what
  • only ActivityPub endpoints error if ap.ghost.org is unreachable

Fix it (safe + minimal): change those two proxy_pass lines to runtime DNS resolution

1) Make a backup (stored under /home/adam/ like you want)

sudo mkdir -p /home/adam/backups/nginx
sudo cp -a /etc/nginx/sites-enabled/sportsaspects.com-ssl.conf \
"/home/adam/backups/nginx/sportsaspects.com-ssl.conf.$(date +%Y%m%d-%H%M%S).bak"

2) Edit the vhost

sudo nano /etc/nginx/sites-enabled/sportsaspects.com-ssl.conf

In both of these blocks:

  • location ~ /.ghost/activitypub/* { ... }
  • location ~ /.well-known/(webfinger|nodeinfo) { ... }

replace:

proxy_pass https://ap.ghost.org;

with:

resolver 127.0.0.53 ipv6=on valid=300s;
resolver_timeout 5s;
set $ap_host ap.ghost.org;
proxy_ssl_name $ap_host;
proxy_pass https://$ap_host;

Leave your existing headers and proxy_ssl_server_name on; as-is.

3) Test + reload

sudo nginx -t && sudo systemctl reload nginx

That hardens the config so nginx can always start, even if DNS is flaky at boot/restart.


Add the “why was it down for 1.5 days?” prevention

Even before the config hardening, nginx could’ve recovered if systemd kept retrying. Let’s make sure it does.

sudo mkdir -p /etc/systemd/system/nginx.service.d

sudo tee /etc/systemd/system/nginx.service.d/override.conf >/dev/null <<'EOF'
[Unit]
After=network-online.target
Wants=network-online.target

[Service]
Restart=on-failure
RestartSec=5s
EOF

sudo systemctl daemon-reload
sudo systemctl restart nginx

Now, even if something transient happens, nginx won’t just stay dead silently.


Now check MCWAI: it may be the same exact issue

On the MCWAI server, run:

sudo systemctl --no-pager -l status nginx
sudo grep -R --line-number 'ap\.ghost\.org' /etc/nginx/sites-enabled /etc/nginx/sites-available 2>/dev/null || true
sudo journalctl -b -1 -u nginx --no-pager | tail -n 200

If you see ap.ghost.org in its vhost too, apply the same patch there.


About the tar.gz

I don’t need it unless MCWAI ends up being different. Your pasted output already showed:

  • the trigger (apt-daily-upgrade)
  • the failure reason (DNS resolution of ap.ghost.org)
  • the exact line in config

Go ahead and paste the MCWAI terminal output now — I’m expecting either the same ap.ghost.org startup failure, or a different hostname-related upstream issue.

You said:

I'll show you my mcwai terminal adam@mcwai:~$ systemctl list-timers --all | head -n 50 NEXT LEFT LAST PASSED UNIT ACTIVATES Sun 2026-02-01 18:30:00 EST 2min 55s Sun 2026-02-01 18:20:11 EST 6min ago sysstat-collect.timer sysstat-collect.service Sun 2026-02-01 22:37:33 EST 4h 10min Sun 2026-02-01 11:47:11 EST 6h ago apt-daily.timer apt-daily.service Mon 2026-02-02 00:00:00 EST 5h 32min Sun 2026-02-01 00:00:22 EST - dpkg-db-backup.timer dpkg-db-backup.service Mon 2026-02-02 00:00:00 EST 5h 32min Sun 2026-02-01 00:00:22 EST - logrotate.timer logrotate.service Mon 2026-02-02 00:07:00 EST 5h 39min - - sysstat-summary.timer sysstat-summary.service Mon 2026-02-02 00:43:06 EST 6h Mon 2026-01-26 01:25:23 EST - fstrim.timer fstrim.service Mon 2026-02-02 01:20:26 EST 6h Sun 2026-02-01 06:27:28 EST - man-db.timer man-db.service Mon 2026-02-02 03:37:17 EST 9h Sun 2026-02-01 17:37:21 EST 49min ago certbot.timer certbot.service Mon 2026-02-02 06:43:38 EST 12h Sun 2026-02-01 06:27:22 EST - apt-daily-upgrade.timer apt-daily-upgrade.service Mon 2026-02-02 08:00:43 EST 13h Sun 2026-02-01 15:07:21 EST 3h 19min ago motd-news.timer motd-news.service Mon 2026-02-02 09:30:11 EST 15h Sun 2026-02-01 09:30:11 EST 8h ago update-notifier-download.timer update-notifier-download.service Mon 2026-02-02 09:39:47 EST 15h Sun 2026-02-01 09:39:47 EST 8h ago systemd-tmpfiles-clean.timer systemd-tmpfiles-clean.service Sun 2026-02-08 03:10:06 EST 6 days Sun 2026-02-01 03:10:22 EST - e2scrub_all.timer e2scrub_all.service Thu 2026-02-12 21:36:13 EST 1 week 4 days Sun 2026-02-01 06:27:28 EST - update-notifier-motd.timer update-notifier-motd.service - - - - apport-autoreport.timer apport-autoreport.service - - - - snapd.snap-repair.timer snapd.snap-repair.service - - - - ua-timer.timer ua-timer.service 17 timers listed. adam@mcwai:~$ sudo systemctl --no-pager -l status nginx sudo grep -R --line-number 'ap\.ghost\.org' /etc/nginx/sites-enabled /etc/nginx/sites-available 2>/dev/null || true sudo journalctl -b -1 -u nginx --no-pager | tail -n 200 ● nginx.service - A high performance web server and a reverse proxy server Loaded: loaded (/usr/lib/systemd/system/nginx.service; enabled; preset: enabled) Active: active (running) since Sun 2026-02-01 09:24:49 EST; 9h ago Docs: man:nginx(8) Process: 824 ExecStartPre=/usr/sbin/nginx -t -q -g daemon on; master_process on; (code=exited, status=0/SUCCESS) Process: 863 ExecStart=/usr/sbin/nginx -g daemon on; master_process on; (code=exited, status=0/SUCCESS) Main PID: 884 (nginx) Tasks: 4 (limit: 4537) Memory: 9.4M (peak: 22.8M) CPU: 1.592s CGroup: /system.slice/nginx.service ├─884 "nginx: master process /usr/sbin/nginx -g daemon on; master_process on;" ├─885 "nginx: worker process" ├─886 "nginx: worker process" └─887 "nginx: worker process" Feb 01 09:24:49 mcwai systemd[1]: Starting nginx.service - A high performance web server and a reverse proxy server... Feb 01 09:24:49 mcwai systemd[1]: Started nginx.service - A high performance web server and a reverse proxy server. /etc/nginx/sites-enabled/myconversationswith.ai.conf:18: proxy_pass https://ap.ghost.org; /etc/nginx/sites-enabled/myconversationswith.ai.conf:28: proxy_pass https://ap.ghost.org; /etc/nginx/sites-available/myconversationswith.ai.conf:18: proxy_pass https://ap.ghost.org; /etc/nginx/sites-available/myconversationswith.ai.conf:28: proxy_pass https://ap.ghost.org; Jan 27 14:05:17 mcwai systemd[1]: Starting nginx.service - A high performance web server and a reverse proxy server... Jan 27 14:05:17 mcwai systemd[1]: Started nginx.service - A high performance web server and a reverse proxy server. Feb 01 06:27:28 mcwai systemd[1]: Stopping nginx.service - A high performance web server and a reverse proxy server... Feb 01 06:27:28 mcwai nginx[839029]: 2026/02/01 06:27:28 [emerg] 839029#839029: host not found in upstream "ap.ghost.org" in /etc/nginx/sites-enabled/myconversationswith.ai.conf:18 Feb 01 06:27:28 mcwai nginx[839029]: nginx: configuration file /etc/nginx/nginx.conf test failed Feb 01 06:27:29 mcwai systemd[1]: nginx.service: Control process exited, code=exited, status=1/FAILURE Feb 01 06:27:29 mcwai systemd[1]: nginx.service: Failed with result 'exit-code'. Feb 01 06:27:29 mcwai systemd[1]: Failed to start nginx.service - A high performance web server and a reverse proxy server. adam@mcwai:~$

ChatGPT said:

Yep — MCWAI is the same exact incident as SportsAspects, just a different trigger time.

What happened on MCWAI (confirmed)

Your previous-boot log shows it plainly:

  • 06:27:28 AM: systemd stopped nginx
  • nginx tried to start and failed config test because it could not resolve ap.ghost.org:
host not found in upstream "ap.ghost.org" in /etc/nginx/sites-enabled/myconversationswith.ai.conf:18

Then later (after you rebooted or otherwise got it restarted), nginx came back up at:

Active: active (running) since Sun 2026-02-01 09:24:49 EST

So both servers were down for the same reason:
✅ Some scheduled maintenance restarted nginx
✅ at that exact moment DNS resolution for ap.ghost.org failed
✅ nginx refused to start and stayed down until you intervened.

This is not “NGINX crashed.” It’s “NGINX was restarted and failed hard on a transient DNS lookup.”


Why both got hit

On MCWAI, the failure happened at 06:27 AM. Look at your timers list: you have man-db.timer showing last run at 06:27:28 and update-notifier-motd also at 06:27:28. That’s a smoking gun for “timer activity around that minute.”

But the more important point: any timer that restarts nginx is enough (unattended upgrades, certbot hooks, admin scripts). On SportsAspects, we already proved it was apt-daily-upgrade. On MCWAI we didn’t yet print the matching unit logs at that time, but we don’t actually need perfect attribution — the fix is the same.


Fix this permanently (do this on BOTH servers)

You already proved both configs include proxy_pass https://ap.ghost.org;. That’s the fragility.

Goal

Make nginx start even if ap.ghost.org DNS is temporarily unreachable, by forcing resolution at request-time.

Step 1 — Backup the vhost

MCWAI:

sudo mkdir -p /home/adam/backups/nginx
sudo cp -a /etc/nginx/sites-enabled/myconversationswith.ai.conf \
"/home/adam/backups/nginx/myconversationswith.ai.conf.$(date +%Y%m%d-%H%M%S).bak"

SportsAspects:

sudo mkdir -p /home/adam/backups/nginx
sudo cp -a /etc/nginx/sites-enabled/sportsaspects.com-ssl.conf \
"/home/adam/backups/nginx/sportsaspects.com-ssl.conf.$(date +%Y%m%d-%H%M%S).bak"

Step 2 — Patch the ap.ghost.org proxy_pass lines

On MCWAI

Edit:

sudo nano /etc/nginx/sites-enabled/myconversationswith.ai.conf

Find the two blocks that contain:

proxy_pass https://ap.ghost.org;

Replace each one with:

resolver 127.0.0.53 ipv6=on valid=300s;
resolver_timeout 5s;
set $ap_host ap.ghost.org;
proxy_ssl_name $ap_host;
proxy_pass https://$ap_host;

On SportsAspects

Same idea (you already showed the exact lines 40 and 50):

sudo nano /etc/nginx/sites-enabled/sportsaspects.com-ssl.conf

Replace both proxy_pass https://ap.ghost.org; lines with the exact same resolver + variable form above.

Why this works

NGINX won’t try to resolve ap.ghost.org during config load anymore. It resolves when those ActivityPub-related endpoints are actually requested. If DNS is down then, only those endpoints fail — not your whole site.


Step 3 — Test and reload

Do on each server after editing:

sudo nginx -t && sudo systemctl reload nginx


Add a safety net: nginx shouldn’t stay dead for hours

This takes 30 seconds and prevents “nginx died and nobody noticed”:

sudo mkdir -p /etc/systemd/system/nginx.service.d

sudo tee /etc/systemd/system/nginx.service.d/override.conf >/dev/null <<'EOF'
[Unit]
After=network-online.target
Wants=network-online.target

[Service]
Restart=on-failure
RestartSec=5s
EOF

sudo systemctl daemon-reload
sudo systemctl restart nginx

Now if nginx ever fails due to a transient event, it will keep retrying.


KM-level explanation of “why DNS failed”

You’re using systemd-resolved with Hetzner-ish resolvers (that IPv6 2a01:4ff:ff00::add:* set). Most of the time it’s fine. But a short DNS hiccup (provider resolver blip, transient network path issue, or resolved restarting) is all it takes at the wrong time.

It does not suggest compromise by itself. The failure is deterministic and explained by logs.


Optional: pin it down exactly which unit stopped nginx at 06:27

If you want the exact culprit on MCWAI, run:

sudo journalctl --since "2026-02-01 06:25" --until "2026-02-01 06:30" --no-pager | egrep -i 'nginx|apt|unattended|certbot|man-db|update-notifier|motd|reload|restart|stop'

But again — it doesn’t change the fix.


About the tar.gz

I don’t need it. You already captured the critical evidence in the terminal paste (line numbers, timestamps, trigger window, and config lines). Keep the tarballs for your own incident archive.

If you apply the resolver+variable patch on both servers, this exact outage mode should stop happening.

You said:

I didn't know I had any kind of auto update stuff going on, I don't want that, I update manually. I have not applied your fixes yet. But I'm about when I get settled some place comfortable besides this parking lot. adam@mcwai:~$ sudo journalctl --since "2026-02-01 06:25" --until "2026-02-01 06:30" --no-pager | egrep -i 'nginx|apt|unattended|certbot|man-db|update-notifier|motd|reload|restart|stop' Feb 01 06:27:22 mcwai systemd[1]: Starting apt-daily-upgrade.service - Daily apt upgrade and clean activities... Feb 01 06:27:25 mcwai apt.systemd.daily[838581]: /usr/bin/unattended-upgrade:567: DeprecationWarning: This process (pid=838581) is multi-threaded, use of fork() may lead to deadlocks in the child. Feb 01 06:27:25 mcwai apt.systemd.daily[838581]: pid = os.fork() Feb 01 06:27:28 mcwai systemd[1]: Reexecuting requested from client PID 838948 ('systemctl') (unit apt-daily-upgrade.service)... Feb 01 06:27:28 mcwai systemd[1]: Starting update-notifier-motd.service - Check to see whether there is a new version of Ubuntu available... Feb 01 06:27:28 mcwai systemd[1]: Starting man-db.service - Daily man-db regeneration... Feb 01 06:27:28 mcwai systemd[1]: update-notifier-motd.service: Deactivated successfully. Feb 01 06:27:28 mcwai systemd[1]: Finished update-notifier-motd.service - Check to see whether there is a new version of Ubuntu available. Feb 01 06:27:28 mcwai systemd[1]: Stopping fail2ban.service - Fail2Ban Service... Feb 01 06:27:28 mcwai systemd[1]: Stopping mysql.service - MySQL Community Server... Feb 01 06:27:28 mcwai systemd[1]: Stopping nginx.service - A high performance web server and a reverse proxy server... Feb 01 06:27:28 mcwai systemd[1]: Stopping ssh.service - OpenBSD Secure Shell server... Feb 01 06:27:28 mcwai systemd[1]: Stopping systemd-journald.service - Journal Service... Feb 01 06:27:28 mcwai systemd-journald[335]: Journal stopped Feb 01 06:27:28 mcwai systemd[1]: Stopped ssh.service - OpenBSD Secure Shell server. Feb 01 06:27:28 mcwai systemd[1]: Stopped systemd-networkd-wait-online.service - Wait for Network to be Configured. Feb 01 06:27:28 mcwai systemd[1]: Stopping systemd-networkd-wait-online.service - Wait for Network to be Configured... Feb 01 06:27:28 mcwai systemd[1]: Stopping systemd-networkd.service - Network Configuration... Feb 01 06:27:28 mcwai systemd[1]: Stopping systemd-resolved.service - Network Name Resolution... Feb 01 06:27:28 mcwai systemd[1]: Stopping systemd-timesyncd.service - Network Time Synchronization... Feb 01 06:27:28 mcwai systemd[1]: Stopped systemd-resolved.service - Network Name Resolution. Feb 01 06:27:28 mcwai systemd[1]: Stopped systemd-journald.service - Journal Service. Feb 01 06:27:28 mcwai systemd[1]: Stopped systemd-timesyncd.service - Network Time Synchronization. Feb 01 06:27:28 mcwai systemd[1]: Stopped systemd-networkd.service - Network Configuration. Feb 01 06:27:28 mcwai systemd[1]: Stopping systemd-udevd.service - Rule-based Manager for Device Events and Files... Feb 01 06:27:28 mcwai systemd[1]: nginx.service: Deactivated successfully. Feb 01 06:27:28 mcwai systemd[1]: Stopped nginx.service - A high performance web server and a reverse proxy server. Feb 01 06:27:28 mcwai systemd[1]: nginx.service: Consumed 19.019s CPU time, 43.5M memory peak, 0B memory swap peak. Feb 01 06:27:28 mcwai systemd[1]: Starting nginx.service - A high performance web server and a reverse proxy server... Feb 01 06:27:28 mcwai systemd[1]: Stopped systemd-udevd.service - Rule-based Manager for Device Events and Files. Feb 01 06:27:28 mcwai nginx[839029]: 2026/02/01 06:27:28 [emerg] 839029#839029: host not found in upstream "ap.ghost.org" in /etc/nginx/sites-enabled/myconversationswith.ai.conf:18 Feb 01 06:27:28 mcwai nginx[839029]: nginx: configuration file /etc/nginx/nginx.conf test failed Feb 01 06:27:29 mcwai systemd[1]: nginx.service: Control process exited, code=exited, status=1/FAILURE Feb 01 06:27:29 mcwai systemd[1]: nginx.service: Failed with result 'exit-code'. Feb 01 06:27:29 mcwai systemd[1]: Failed to start nginx.service - A high performance web server and a reverse proxy server. Feb 01 06:27:29 mcwai systemd[1]: man-db.service: Deactivated successfully. Feb 01 06:27:29 mcwai systemd[1]: Finished man-db.service - Daily man-db regeneration. Feb 01 06:27:29 mcwai systemd[1]: Stopped fail2ban.service - Fail2Ban Service. Feb 01 06:27:31 mcwai systemd[1]: Stopped mysql.service - MySQL Community Server. Feb 01 06:27:34 mcwai systemd[1]: apt-daily-upgrade.service: Deactivated successfully. Feb 01 06:27:34 mcwai systemd[1]: Finished apt-daily-upgrade.service - Daily apt upgrade and clean activities. Feb 01 06:27:34 mcwai systemd[1]: apt-daily-upgrade.service: Consumed 7.986s CPU time, 218.1M memory peak, 0B memory swap peak. adam@mcwai:~$

ChatGPT said:

Thought for 1m 27s on Sun Feb 1, 2026 — 6:36 PM ET

That log is gold — it proves this wasn’t “nginx randomly crashed.” It was automatic upgrades doing a systemd re-exec, which temporarily took DNS (systemd-resolved) down, and nginx failed to start because your config requires resolving ap.ghost.org at startup.

What your snippet actually shows

  • apt-daily-upgrade.service starts → that runs unattended-upgrade
  • Then: Reexecuting requested ... ('systemctl') (unit apt-daily-upgrade.service)...
  • You can literally see it stopping core services, including systemd-resolved (DNS) and nginx
  • Immediately after: nginx tries to start and dies with:
    • host not found in upstream "ap.ghost.org"

So the sequence is:

auto upgrade → systemd reexec → DNS stub goes down briefly → nginx restart happens during that → nginx config can’t resolve ap.ghost.org → nginx stays down

Same pattern on both servers.


If you hate auto updates (manual only): disable them cleanly

You’ve got two layers:

  1. Timers/services that trigger periodic apt work
  2. APT config that enables unattended upgrades

Do this on both servers.

1) Disable the timers (the “engine”)

sudo systemctl disable --now apt-daily.timer apt-daily-upgrade.timer
sudo systemctl mask apt-daily.timer apt-daily-upgrade.timer

Optional (noise reducers; not upgrades but you may not want them):

sudo systemctl disable --now motd-news.timer update-notifier-download.timer update-notifier-motd.timer 2>/dev/null || true
sudo systemctl mask motd-news.timer update-notifier-download.timer update-notifier-motd.timer 2>/dev/null || true

Confirm:

systemctl list-timers --all | egrep 'apt-daily|unattended|motd|update-notifier' || true

2) Turn off unattended upgrades in APT config (the “permission”)

Check what’s set:

cat /etc/apt/apt.conf.d/20auto-upgrades 2>/dev/null || true

Edit it:

sudo nano /etc/apt/apt.conf.d/20auto-upgrades

Set both to 0 (create the file if it doesn’t exist):

APT::Periodic::Update-Package-Lists "0";
APT::Periodic::Unattended-Upgrade "0";

Also check (sometimes exists):

ls -lah /etc/apt/apt.conf.d/10periodic 2>/dev/null || true
cat /etc/apt/apt.conf.d/10periodic 2>/dev/null || true

If it has periodic settings, set them to 0 as well.

3) (Optional) remove the package entirely

If you want it gone:

sudo apt purge -y unattended-upgrades

Reality check: turning off auto updates is a security tradeoff. If you do this, you should adopt a simple manual routine (weekly is fine):

sudo apt update && sudo apt upgrade


Even if you disable auto updates, you still need to fix nginx

Because this can also happen during manual restarts/reloads, reboots, certbot reloads, etc.

Your nginx vhost has:

proxy_pass https://ap.ghost.org;

in ActivityPub-related locations. That makes nginx’s ability to start depend on DNS being up right then.

Two valid choices

Option A (recommended): keep ActivityPub but remove the startup fragility
Change those proxy_pass lines to runtime DNS resolution (so nginx can always start).

For each location block that proxies to ap.ghost.org, replace:

proxy_pass https://ap.ghost.org;

with:

resolver 127.0.0.53 ipv6=on valid=300s;
resolver_timeout 5s;
set $ap_host ap.ghost.org;
proxy_ssl_name $ap_host;
proxy_pass https://$ap_host;

Then:

sudo nginx -t && sudo systemctl reload nginx

Option B: you don’t care about ActivityPub at all
Delete/comment out those ActivityPub + well-known location blocks entirely and reload nginx. Zero dependency on ap.ghost.org.


Add a safety net so nginx doesn’t stay dead for hours

This makes systemd retry nginx automatically after transient failures:

sudo mkdir -p /etc/systemd/system/nginx.service.d

sudo tee /etc/systemd/system/nginx.service.d/override.conf >/dev/null <<'EOF'
[Unit]
After=network-online.target
Wants=network-online.target

[Service]
Restart=on-failure
RestartSec=5s
EOF

sudo systemctl daemon-reload
sudo systemctl restart nginx


Bottom line (so you can relax a bit)

This looks like a self-inflicted reliability bug caused by:

  • unattended upgrades restarting services
  • nginx config that hard-depends on external DNS during startup

It does not smell like compromise.

When you’re ready, the right order is:

  1. disable auto upgrade timers/config (if you want manual only)
  2. patch nginx ap.ghost.org blocks (so DNS hiccups can’t take the whole site down)
  3. add nginx restart-on-failure override

If you want, paste the two ActivityPub blocks from myconversationswith.ai.conf (lines around 1–40) and I’ll tell you exactly where to drop the replacement snippet with minimal editing.