Missing packages: (nginx) when running ghost doctor

All,

I received the following error when trying to run ghost doctor after discovering that my ghost blog was unresponsive:

One or more errors occurred. 1) Checking system compatibility Message: System stack checks failed with message: ‘Missing package(s): nginx’

  • Debug Information: OS: Ubuntu, v24.04.4
  • LTS Node Version: v22.22.3
  • Ghost Version: 6.44.1
  • Ghost-CLI Version: 1.29.3
  • Environment: production Command: ‘ghost doctor’

Claude Opus 4.8 to the rescue! Claude helped me identify the issue and it was some kind of momentary DNS failure after an upgrade or update. Restarting the nginx service fixed the issue and I’ll put the fix and code Claude recommended below in case you want to make use of it for your servers.

sudo systemctl start nginx
sudo systemctl status nginx

That brought my server back up and Claude recommended the following changes to ensure it wouldn’t happen again:

sudo systemctl edit nginx

In the editor, put the following near the top of the file:

[Service]
Restart=on-failure
RestartSec=15

This was the entry from journalctl -u nginx [edited] that indicated that it was a DNS failure that caused the initial issue:

[emerg] host not found in upstream "ap.ghost.org" in /etc/nginx/sites-enabled/nolongersmartest.org-ssl.conf:24

If you have the above error when you run ghost doctor, before you panic and consider backing up your server and attempting to reinstall or mess with nginx, try restarting the nginx first. If that fixes it, consider making the change to the nginx systemctl config to stop it from happening in the future.

A little feedback to the Ghost team:

  1. That error message from ghost doctor sounds pretty scary and catastrophic compared to what’s going on. Is it possible to have the ghost doctor dig a little to see if a simple restart may do the trick? I’d be happy to contribute if someone could point me in the right direction.
  2. Any potential down side to making the systemctl nginx config changes recommended by Claude? If not, would it make sense to incorporate this? If so, any other fixes you recommend?

I hope this helps people going forward.

Kind regards,

Ted

I asked Claude for specific recommendations for updating Ghost instead of applying the band-aid. Here’s Claude’s suggestion:

nginx fails to start on an unresolvable ap.ghost.org upstream → ghost doctor reports it as “Missing package(s): nginx”

Environment

  • Ghost 6.44.1 · Ghost-CLI 1.29.3 · Node v22.22.3 · MySQL
  • Ubuntu 24.04.4 LTS · self-hosted, production · installed via Ghost-CLI

Summary

After an unattended nginx security upgrade restarted the service, nginx failed to start because it couldn’t resolve the ap.ghost.org ActivityPub upstream at that moment. Since nginx resolves literal proxy_pass hostnames once at startup and aborts if the lookup fails, the entire site went down — not just the Fediverse path. ghost doctor then reported this as Missing package(s): nginx, even though the nginx package was installed the whole time. The message points toward reinstalling nginx, when the actual fix was a single systemctl start nginx once DNS recovered.

Reproduction

  1. Standard Ghost-CLI production install. Ghost-CLI writes an ap.ghost.org upstream into <site>-ssl.conf (added in Ghost-CLI #1963).
  2. Restart nginx while ap.ghost.org is momentarily unresolvable — e.g. the auto-triggered restart of an unattended-upgrades nginx update landing on a brief DNS gap (more likely on home/rural connections).
  3. nginx aborts startup; the site is fully down.
  4. ghost doctorChecking system compatibility … Missing package(s): nginx, despite dpkg -l nginx showing ii.

Evidence

Package is present and the binary works:

$ dpkg -l nginx | grep nginx
ii  nginx  1.24.0-2ubuntu7.11  amd64  ...
$ nginx -v
nginx version: nginx/1.24.0 (Ubuntu)

Service is failed — note the config test (ExecStartPrenginx -t) passes; only the real start fails:

ExecStartPre=/usr/sbin/nginx -t ...   (code=exited, status=0/SUCCESS)
ExecStart=/usr/sbin/nginx ...         (code=exited, status=1/FAILURE)
Active: failed (Result: exit-code)

The actual cause, from journalctl -u nginx:

[emerg] host not found in upstream "ap.ghost.org" in /etc/nginx/sites-enabled/<site>-ssl.conf:24

The split is the tell: nginx -t validates config and loads certs but does not resolve proxy_pass upstreams, so only the real start hits the DNS failure.

Root cause

  1. Config — a literal upstream hostname makes a momentary DNS failure fatal to the whole web server, and ties a self-hosted install’s availability to a Ghost-operated hostname being resolvable at the exact moment nginx restarts.
  2. Diagnosticsghost doctor collapses “package not installed”, “installed but not running”, and “installed but failed to start” into one misleading Missing package(s) message, and hides the journal line that actually explains the failure.

Recommendations (prioritized)

Ghost-CLI config generation

  1. Generate the ActivityPub upstream for request-time resolution — a resolver directive plus a variable in proxy_pass (set $ap ``ap.ghost.org``; proxy_pass ``https://$ap``;) instead of the literal hostname. Highest-impact change: a DNS blip then degrades only the ActivityPub path (502 on those requests) instead of preventing nginx from starting. Converts a site outage into a contained feature degradation.
  2. Gate the upstream on the ActivityPub feature being enabled (or otherwise fail soft), so installs not using the Fediverse don’t carry a hard startup dependency for zero benefit.

ghost doctor

  1. Separate the checks — package presence (dpkg/binary) vs service active (systemctl is-active) vs failed-to-start — and report the true state instead of Missing package(s).
  2. Surface the underlying error — echo the last journalctl -u nginx line so the real cause (host not found in upstream …) is visible immediately.
  3. Add a targeted hint for the known host not found in upstream "ap.ghost.org" signature: suggest checking DNS / restarting nginx before any reinstall.
  4. Re-word the message/severity so “installed but failed to start” isn’t presented as a missing package — that framing is what pushes users toward reinstalling nginx (which risks clobbering the generated vhost config).

Underlying principle

Self-hosted installs shouldn’t fail hard on resolution of a Ghost-operated hostname. External dependencies added to generated configs should fail soft — degrade the dependent feature rather than block the whole server.

Local workaround for others hitting this

First, just restart — the site is usually one command from recovery:

sudo systemctl start nginx

To auto-recover from future transient blips, add a systemd drop-in (sudo systemctl edit nginx):

[Service]
Restart=on-failure
RestartSec=15

Caveats worth knowing: this is a coarser mitigation than the config fix above — it shortens but doesn’t eliminate downtime during a blip (nginx-won’t-start is all-or-nothing), and it can make a genuine persistent failure quieter by looping instead of resting in failed, so monitor the live site rather than just systemctl is-active. The 15-second spacing is deliberate: it sits above systemd’s default limit of 5 starts per 10 seconds, so the retry loop won’t trip the rate limiter and park in a failed state.


Happy to open a PR for the request-time-resolution config change and/or the ghost doctor reporting split if a maintainer can point me at the right spots.