Ghost 6.72.0
Overview
Ghost is a free, open-source publishing platform written in Node.js. It offers a fast editor, posts and pages, themes, tags, staff accounts with roles, and built-in members and newsletters. The admin lives at /ghost/ on your site.
This guide covers the Ghost 6.72.0 on Ubuntu 24.04 AMI from SecureAnyCloud on Amazon EC2. It is one software version of the SecureAnyCloud BookStack, CMS, Blogs and Wikis listing on AWS Marketplace, which you select when you subscribe and launch.
What you get:
- No open setup wizard, no shared database, no shared keys. A stock Ghost asks the first visitor to
/ghost/to create the site owner, so whoever reaches a new site first owns it. This image closes that gap. It ships no database (the MySQL data directory is empty) and no secret in Ghost's configuration. On first boot, your instance:- initialises its own MySQL and creates Ghost's database user with a password generated on the instance;
- runs Ghost once on a random local port, so its migrations generate this site's session secrets, member and staff signing keys and API keys;
- creates the owner account through Ghost's own setup API over the local loopback interface, with a password generated on the instance;
- checks that Ghost reports setup as complete and refuses a second setup.
- Secrets unique to your instance, never on a command line. The owner password, the database password, Ghost's keys and the TLS key are generated on your instance. Nothing from the image build is reused. Passwords travel in files only root can read, or on standard input, never as command-line arguments. The database password reaches Ghost as a systemd credential, so it is not in
config.production.jsonor in Ghost's environment. - The current, verified release. Ghost 6.72.0 is checked file for file against the official npm release and pinned by its checksum. Node.js 22.23.4 (the October 2026 security release) comes from NodeSource's signed repository.
- A mitigation for an unfixed Ghost dependency. Ghost 6.72.0 ships body-parser 1.20.8, which an unauthenticated request with a compressed body can crash (GHSA-27r3-jxmh-p828). nginx refuses every request that carries a
Content-Encodingheader (HTTP 415), because browsers never compress request bodies. systemd restarts Ghost within two seconds if it ever stops. You can lift the mitigation after updating to a Ghost release with body-parser 1.20.9 or later (Step 10). - HTTPS from first boot. nginx serves Ghost on port 443 with a certificate generated on your instance's first boot. Port 80 redirects to HTTPS. Ghost's own address is pinned to your host name, so every link, e-mails included, uses it.
- No calls home and no third-party scripts. Ghost's daily update check, Gravatar, structured data pings, the social web (ActivityPub), Unsplash, web analytics and the Explore ping are off. Portal (sign-up and sign-in), Search, Comments and Ghost's other front-end scripts load from your own server (pinned releases) instead of from jsDelivr. The default cover images that Ghost points at static.ghost.org are removed.
- No outbound connections from the public site. With the image's settings, Ghost made no outbound connection while it started, served pages and published a post. The public site's pages load nothing from other servers. In your staff's own browsers, Ghost's admin still fetches a few public assets: the changelog from ghost.org, default profile pictures from static.ghost.org, font previews from fonts.bunny.net (in Settings > Design & branding) and, when the editor's emoji picker is opened, emoji data from jsDelivr.
- Webmention receipt off. Ghost fetches the source address of every webmention anyone sends it, so the image refuses incoming webmentions. Step 9 shows how to turn them back on.
- Invite-only member sign-up until you configure e-mail. Ghost's members sign in with e-mailed links, and the image has no mail service. Sign-up is set to invite-only. Staff device verification (an e-mailed code on sign-in from a new browser) is off, so the owner is never locked out. Step 8 shows how to turn both on once e-mail works.
- Rate-limited sign-in. Staff sign-in, its verification step, password reset, setup, invitation acceptance and the members magic-link and one-time-code routes share one budget of 20 requests a minute per address (after a burst of 20), in any spelling of the address. Ghost's own lock-out applies on top. nginx uses the address of the connection, never a header a client sends.
- Safe uploads. Files under
/content/images,/content/filesand/content/mediaare sent withnosniffand a sandboxingcontentsecurity policy. Only passive types (images, PDF, fonts, audio, video) open in the browser. Anything else, SVG and HTML included, is sent as a download. The rest of the content folder is never served. - Staff sessions unusable from other sites. Ghost accepts an admin request that carries neither an
Originnor aRefererheader as if it came from the admin itself. nginx requires same-site proof for every admin API request that carries a staff session. A link or image on another site therefore cannot run admin actions, such as Ghost's server-side link preview fetch, with a signed-in editor's session. Admin API integrations, which sign requests with a key, are unaffected. - Hardened defaults. Ghost runs as its own
ghostaccount, which can write only thecontentfolder. The code, configuration and ghost-cli record are read-only to it. MySQL has no network listener (Ghost uses its local socket), itsrootaccount signs in only through the socket as the operating system's root, and its binary and general logs are off. Ghost, nginx, MySQL and the first-boot service are blocked from the instance metadata service, the AMI requires IMDSv2, and requests for a host name the instance does not serve are dropped. - Fully patched Ubuntu 24.04 LTS at build time, with unattended security upgrades switched on.
- On the image: Ghost 6.72.0 with ghost-cli 1.34.0, Node.js 22.23.4, MySQL 8.0, nginx, Ubuntu 24.04 LTS, cloud-init and the AWS Systems Manager agent.
- Instance defaults: default user
ubuntu, x86_64 architecture, recommended instance type m5.large.
Prerequisites
- An AWS account subscribed to the listing, an EC2 key pair, and a VPC subnet with a route to the internet (or a private route you use for management).
- A security group that allows TCP 22 (SSH), TCP 443 (Ghost) and TCP 80 (redirects to HTTPS) from the networks that should reach them. Restrict port 22 to your own networks. Open port 443 to your readers for a public site, or only to your team while you set it up.
Launch the instance
In the AWS Marketplace listing, choose Continue to Subscribe, then Continue to Configuration. Pick Ghost 6.72.0 on Ubuntu 24.04 as the software version and select your Region. Choose Launch through EC2, then select m5.large, your key pair and a security group as described above.
To launch with the AWS CLI instead, replace the values in angle brackets:
aws ec2 run-instances --region <region> --image-id <ami-id-from-the-listing> \
--instance-type m5.large --key-name <your-key-pair> \
--security-group-ids <your-security-group> --subnet-id <your-subnet> \
--metadata-options HttpTokens=required,HttpEndpoint=enabled \
--tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=ghost}]'First boot finishes about one to two minutes after the instance is running, because Ghost builds its database on the first start.
Connect to your instance
Connect over SSH. The SSH user for Ghost 6.72.0 on Ubuntu 24.04 is ubuntu.
ssh -i <your-key>.pem ubuntu@<public-ip-or-dns>Check the services
Confirm that MySQL, Ghost (the ghost_sac service) and nginx are all active:
systemctl is-active mysql ghost_sac nginxactive active activeAs the ubuntu user, manage Ghost with ghost-cli from its folder:
cd /var/www/ghost && ghost versionGhost-CLI version: 1.34.0 Ghost version: 6.72.0 (at /var/www/ghost)Only SSH, HTTP and HTTPS listen on the network. Ghost listens on the loopback interface behind nginx, and MySQL has no TCP listener at all:
sudo ss -tlnH | awk '{print $4}' | sort -u0.0.0.0:22 0.0.0.0:443 0.0.0.0:80 127.0.0.1:2368 127.0.0.53%lo:53 127.0.0.54:53 [::]:22 [::]:443 [::]:80Get your sign-in details
Read the credentials file as root. It holds the address Ghost is served as (your instance's public DNS name by default), the admin address, and the owner's sign-in e-mail and password. All of these are generated on this instance.
sudo cat /root/ghost-credentials.txt# Ghost on this instance (sac). Root-only (0600). Generated at first boot: 2026-10-28T00:12:25Z GHOST_URL=https://ec2-52-201-222-176.compute-1.amazonaws.com/ GHOST_ADMIN_URL=https://ec2-52-201-222-176.compute-1.amazonaws.com/ghost/ GHOST_OWNER_EMAIL=owner@example.invalid GHOST_OWNER_PASSWORD=<GHOST_OWNER_PASSWORD>The owner signs in with the e-mail owner@example.invalid, an address that can never receive mail. After you sign in, change it to your own address under Settings > Staff (Step 5).
Visit the site and sign in
Open GHOST_URL in a browser. The certificate is self-signed, so the browser warns once. Continue to the site. Step 11 replaces the certificate with your own. Visiting the instance by its IP address redirects to the configured name.
Open GHOST_ADMIN_URL and sign in with the owner e-mail and password. Ghost's admin opens with Posts, Pages, Tags, Members and Settings. Start with these settings:
- Settings > Staff: open your own profile, set your name and real e-mail address, and change the password if you like.
- Settings > General: set the site title, description and time zone.
- Settings > Design & branding: choose and customise a theme (Source is active).
To publish, select Posts > New post, write your content, then select Publish. The post appears on the home page at once.
Check HTTPS and the setup state
Confirm that port 80 redirects to HTTPS on the configured name:
curl -s -o /dev/null -w '%{http_code} %{redirect_url}\n' http://127.0.0.1/301 https://ec2-52-201-222-176.compute-1.amazonaws.com/Then confirm that the setup API reports setup as complete and refuses to run again. This is what stops anyone else from taking over the site:
H=$(sed -n 's/^set \$ghost_host "\(.*\)";$/\1/p' /etc/nginx/ghost-host.conf)
curl -sk --resolve "$H:443:127.0.0.1" "https://$H/ghost/api/admin/authentication/setup/"; echo
curl -sk --resolve "$H:443:127.0.0.1" -o /dev/null -w 'second setup: %{http_code}\n' -H 'Content-Type: application/json' \
-d '{"setup":[{"name":"x","email":"x@example.invalid","password":"Xx1234567890abc","blogTitle":"x"}]}' \
"https://$H/ghost/api/admin/authentication/setup/"{"setup":[{"status":true}]} second setup: 403Your data
Ghost keeps its content in two places:
- the MySQL database
ghost_prod, which holds posts, members, settings and keys; - the folder
/var/www/ghost/content, which holds uploaded images, files and media, and your themes.
Back up both, for example with a snapshot of the instance's EBS volume, or with these commands:
sudo mysqldump --single-transaction ghost_prod | gzip > ghost_prod-$(date +%F).sql.gz
sudo tar -C /var/www/ghost -czf ghost-content-$(date +%F).tar.gz contentGhost's own export (Settings > Advanced > Migration tools) saves posts and settings as JSON. Keep backups private, because the database holds your members' e-mail addresses and the keys that sign sessions.
E-mail, members sign-up and device verification
Ghost sends staff password resets, member sign-in links and newsletters by e-mail. The image does not configure a mail service, so set one up first. For transactional mail, add an SMTP server (for example Amazon SES) to /var/www/ghost/config.production.json by replacing the "mail" block:
"mail": { "transport": "SMTP", "from": "Your Site <noreply@your-domain>", "options": { "host": "email-smtp.<region>.amazonaws.com", "port": 587, "secure": false, "auth": { "user": "<smtp-user>", "pass": "<smtp-password>" } } }Restart Ghost, then test a password reset:
cd /var/www/ghost && ghost restartNewsletters use Mailgun, which you configure under Settings > Newsletters. Ghost's own documentation explains the setup.
Once e-mail works, you can change two defaults:
- Member sign-up: under Settings > Membership > Access, Subscription access is set to Invite-only. Choose Anyone can sign up to open it.
- Staff device verification (an e-mailed code when staff sign in from a new browser) is off. To turn it on, give every staff account a real e-mail address. Then add
"security": { "staffDeviceVerification": true }toconfig.production.json, replacing the existingsecurityblock, and runghost restart. Leave it off if mail does not work, or staff are locked out.
The closed-by-default settings
These settings live in /var/www/ghost/config.production.json. The file holds no password, so ghost-cli can manage it. Each entry below shows the image value and how to restore Ghost's default:
- privacy.useTinfoil is true, so there are no Gravatar, structured data or IndexNow pings. Remove the privacy block to restore the default.
- updateCheck.url points to a closed local port, so there is no daily update check. Remove the updateCheck block to restore the default. Ghost then sends only its version and shows release and security notices in the admin.
- portal, sodoSearch, comments, announcementBar, signupForm and adminToolbar are served from /sac-apps/ on your server. Remove these blocks to load them from jsDelivr.
Other settings are stored in the database. Change them in the admin, where the settings search box finds each one. The social web (Settings > Growth > Network), Unsplash and Pintura (Settings > Advanced > Integrations) and web analytics (Settings > General settings > Analytics) are off. Run ghost restart after editing the configuration.
To list the front-end scripts the image serves and their versions, run:
cat /usr/local/share/sac/ghost-apps/VERSIONSportal 2.72.8 sodo-search 1.9.6 announcement-bar 1.1.679 comments-ui 1.6.448 signup-form 0.4.3 admin-toolbar 0.1.663Webmentions. nginx refuses incoming webmentions. To accept them, remove the if ($ghost_path_refused) line from /etc/nginx/sites-available/sac-ghost (or drop the webmentions line from the map in /etc/nginx/conf.d/sac-ghost-guard.conf) and run sudo nginx -t && sudo systemctl reload nginx. Ghost refuses to fetch private network addresses either way.
Updating Ghost
Take a snapshot or backup first. Then, as the ubuntu user, update Ghost in place with ghost-cli:
cd /var/www/ghost && ghost updateThe compressed-body mitigation. After an update, nginx keeps refusing request bodies with a Content-Encoding header (415). Once ghost version reports a release whose body-parser is 1.20.9 or later (check with grep '"version"' /var/www/ghost/current/node_modules/body-parser/package.json), you may remove the if ($http_content_encoding) { return 415; } lines from /etc/nginx/sites-available/sac-ghost and reload nginx. Keeping them does no harm.
Front-end scripts. After an update, compare the versions in /var/www/ghost/current/core/shared/config/defaults.json (portal.version and the others) with /usr/local/share/sac/ghost-apps/VERSIONS. If Ghost now asks for a newer line (for example portal 2.73), remove the matching block from config.production.json (Step 9) or place that release's umd files in the folder.
Keep Node.js up to date with sudo apt-get update && sudo apt-get upgrade. The NodeSource repository is already configured.
Your own DNS name and certificate
Point a DNS record at the instance, then set the name Ghost is served as. The command below creates a new self-signed certificate for the name and pins it in nginx, so the instance's IP addresses redirect to it. It also sets Ghost's url, restarts Ghost, updates /root/ghost-credentials.txt and keeps the name across reboots:
sudo ghost-sac-set-hostname <your-domain>To use a certificate from your own certificate authority instead, copy it over the generated pair and reload nginx. Running ghost-sac-set-hostname again replaces it with a self-signed certificate.
sudo install -m 0644 <your-domain>.crt /etc/nginx/ssl/ghost.crt
sudo install -m 0600 <your-domain>.key /etc/nginx/ssl/ghost.key
sudo nginx -t && sudo systemctl reload nginxOnce browsers trust the certificate, you can also add an HSTS header to /etc/nginx/snippets/sac-ghost-headers.conf. The image does not send one, because browsers ignore HSTS from a site whose certificate they do not trust.
Behind a load balancer or CloudFront: terminate TLS on it and forward to port 443 on the instance with the configured host name. nginx always uses the connection's address as the client address. With a proxy in front, every request therefore appears to come from the proxy, and Ghost's per-address limits apply to the proxy as a whole.
Security checks
Run the checks below to confirm the defaults in force. The first command confirms that the owner password signs in and that a wrong one is refused with Ghost's own error.
sudo ghost-sac-cred-roundtripOK https 201 200 422H=$(sed -n 's/^set \$ghost_host "\(.*\)";$/\1/p' /etc/nginx/ghost-host.conf) R=(curl -sk --resolve "$H:443:127.0.0.1" -o /dev/null) "${R[@]}" -w 'compressed body %{http_code}\n' -H 'Content-Encoding: gzip' --data-binary x "https://$H/ghost/api/admin/session/" "${R[@]}" -w 'webmention %{http_code}\n' -d 'source=https://example.com/&target=https://example.org/' "https://$H/webmentions/receive/" "${R[@]}" -w 'upper-case upload path %{http_code}\n' "https://$H/CONTENT/images/x.svg" "${R[@]}" -w 'unknown host %{http_code}\n' -H 'Host: other.example' https://127.0.0.1/ ; echocompressed body 415 webmention 404 upper-case upload path 404 unknown host 000A result of 000 means nginx closed the connection without an answer. Next, confirm that Ghost, nginx and MySQL are blocked from the instance metadata service:
systemctl show -p IPAddressDeny ghost_sac nginx mysqlIPAddressDeny=169.254.169.254/32 fd00:ec2::254/128 168.63.129.16/32 IPAddressDeny=169.254.169.254/32 fd00:ec2::254/128 168.63.129.16/32 IPAddressDeny=169.254.169.254/32 fd00:ec2::254/128 168.63.129.16/32Finally, check file ownership. The credentials and the database password are readable by root only, and Ghost's configuration holds no secret. The content folder belongs to Ghost, and the code belongs to the ubuntu user who runs ghost-cli:
sudo stat -c '%a %U:%G %n' /root/ghost-credentials.txt /etc/ghost-sac/db-password /var/www/ghost/config.production.json /var/www/ghost/content /var/www/ghost/current/core600 root:root /root/ghost-credentials.txt 600 root:root /etc/ghost-sac/db-password 664 ubuntu:ubuntu /var/www/ghost/config.production.json 775 ghost:ghost /var/www/ghost/content 755 ubuntu:ubuntu /var/www/ghost/current/corePasswords
ghost-sac-set-password gives the owner a new random password using Ghost's own password hashing. It also signs out every staff session, clears sign-in lock-outs and records the password in /root/ghost-credentials.txt.
Run it without arguments, or pass your own password (12 to 96 letters and digits) on standard input. Staff change their own password under Settings > Staff.
sudo ghost-sac-set-password printf '%s\n' '<your-new-password>' | sudo ghost-sac-set-password --stdinServer components
Use this list to find the main parts of the installation:
- Ghost (ghost-cli instance SecureAnyCloud, owned by ubuntu): /var/www/ghost
- Configuration (no secrets): /var/www/ghost/config.production.json
- Uploads and themes (owned by ghost): /var/www/ghost/content
- Ghost service: /lib/systemd/system/ghost_sac.service
- Database password (root only, a systemd credential for Ghost): /etc/ghost-sac/db-password
- Database (MySQL datadir) and settings: /var/lib/mysql, /etc/mysql/mysql.conf.d/99-sac-ghost.cnf
- nginx site, request limits and cross-site rules: /etc/nginx/sites-available/sac-ghost, /etc/nginx/conf.d/sac-ghost-guard.conf, /etc/nginx/conf.d/sac-ghost-xsite.conf, /etc/nginx/conf.d/sac-xsite-guard.conf, /etc/nginx/snippets/sac-ghost-*.conf
- Front-end scripts served locally: /usr/local/share/sac/ghost-apps/
- Certificate and key: /etc/nginx/ssl/
- First boot and helpers: /usr/local/sbin/ghost-firstboot, ghost-sac-set-hostname, ghost-sac-set-password, ghost-sac-cred-roundtrip
- Licence, release and image notes: /usr/share/doc/ghost-sac/
- Credentials: /root/ghost-credentials.txt (mode 0600)
Security recommendations
- Allow port 22 in the security group from your own networks only.
- Set your real e-mail on the owner account and keep its password secret (Step 13). Give each writer their own staff account with the least role they need (Contributor, Author or Editor).
- Configure e-mail (Step 8), then consider turning on staff device verification.
- Use your own DNS name and a certificate your browsers trust (Step 11).
- Keep Ghost on the newest release (Step 10) and install only themes you trust.
- Keep the operating system patched. Unattended security upgrades are on, and
sudo apt-get update && sudo apt-get upgradeapplies everything else, Node.js included.
Support and licensing
Ghost is free software under the MIT licence. A copy is at /usr/share/doc/ghost-sac/LICENSE.ghost, and the bundled libraries carry their own open-source licences. There is no per-user or per-CPU licence fee.
This image installs the unmodified upstream release, plus the SecureAnyCloud first-boot setup, settings and helpers described above. The AMI price covers SecureAnyCloud's hardening, first-boot setup and support. Ghost is a trademark of the Ghost Foundation. SecureAnyCloud is not affiliated with or endorsed by it.
- Email: sales@secureanycloud.com
Deploy on AWS
Launch Ghost 6.72.0 on Ubuntu 24.04 on AWS with 24/7 support from SecureAnyCloud.
View on Marketplace
Need Help?
Our support team is available 24/7.
sales@secureanycloud.com