Strapi 5.60.0
Overview
Strapi is a free and open-source headless content management system built on Node.js. Editors work in an admin panel at /admin, and your websites, apps and services read and write content through Strapi's REST API under /api. It includes a media library, roles and permissions for admin users and for API users, draft and publish, internationalization and API tokens.
This SecureAnyCloud AMI runs Strapi 5.60.0 on Ubuntu 24.04 on Amazon EC2. It ships Strapi's own JavaScript project with a SQLite database, ready for your content types. It is one version of the SecureAnyCloud CMS, Blogs and Wikis listing on AWS Marketplace, and you pick it as the software version when you subscribe and launch.
What you get:
- No setup page anyone could claim. A fresh Strapi project lets whoever opens the admin panel first register as the super administrator. On this image, your instance's first boot creates the super administrator itself, with a password generated on your instance, before the web server starts. That page never opens.
- Per-instance secrets, never on a command line. Strapi's application keys, API token salt, admin and users JWT secrets, transfer token salt and encryption key, the administrator password and the TLS key are generated on your instance's first boot. Nothing from the image build is reused. The secrets live in one root-owned file that only the Strapi service can read, not in the environment or a
.envfile. The password reaches Strapi in a file, never as a command-line argument (Strapi's ownadmin:create-usercommand takes it on the command line). - HTTPS from the first boot. nginx serves Strapi on port 443 with a certificate generated on your instance's first boot. Port 80 redirects to HTTPS. Strapi itself listens only on
127.0.0.1:1337. - Host from configuration, never from the request. Strapi's public URL (used for links such as password reset) and its CORS origin default to your instance's public DNS name, set by SecureAnyCloud's strapi-set-hostname helper. nginx answers only that name and the instance's own addresses. Any other host name gets no answer.
- Closed by default. Public end-user sign-up (
/api/auth/local/register) is off until you turn it on. Strapi's usage telemetry, its npm update check, its MCP endpoint and Strapi AI are off, and the admin panel does not fetch release notes from GitHub. No mail is sent, and no mail content is written to the log, until you configure an e-mail provider (Step 9). - Community Edition. Strapi's Enterprise code ships inside its npm packages, as in every Strapi install, but it is never enabled. No license key ships, and the service runs with
STRAPI_DISABLE_EE=true. - Sign-in protection. nginx limits every admin and API sign-in, registration, password reset and token refresh route to 10 requests a minute per client address, on top of Strapi's own rate limit. The admin refresh cookie is
Secure,HttpOnlyandSameSite=Lax. - Sandboxed uploads. Every file under
/uploads/getsnosniffand a sandboxing content security policy. Only passive types (images, PDF, fonts, audio and video) open in the browser, and anything else is sent as a download. Strapi refuses HTML, SVG and executable uploads. - A hardened service. Strapi runs as its own unprivileged user. The application in
/opt/strapibelongs to root and is read-only to Strapi, which can write only its data folder,/var/lib/strapi. Instance metadata (169.254.169.254) is blocked for Strapi and nginx, and Strapi refuses webhooks to non-public addresses. WebSocket upgrades are accepted only for Strapi's remote data transfer, and never from another site. - Checked dependencies. Strapi's dependency tree is installed exactly from a reviewed lockfile. The one native module (better-sqlite3) is compiled from source on the build. Only the Node.js runtime itself ships, not npm.
- Fail closed. nginx and Strapi do not start until first boot has finished. If first boot cannot complete, the site stays offline rather than open.
Prerequisites
Before you launch, make sure you have:
- 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 (Strapi) and TCP 80 (redirects to HTTPS) from the networks that should reach them. Keep port 22 open to your own networks only, and open port 443 to your editors and API clients.
Launch the instance
In the AWS Marketplace listing, choose Continue to Subscribe, then Continue to Configuration. Pick Strapi 5.60.0 on Ubuntu 24.04 as the software version, select your Region and choose Launch through EC2. Choose m5.large, your key pair and a security group as described above.
To launch with the AWS CLI instead, run the command below and replace the values in angle brackets with your own:
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=strapi}]'Connect to your instance
For Strapi 5.60.0 on Ubuntu 24.04, the SSH user is ubuntu. Connect with your key pair:
ssh -i <your-key.pem> ubuntu@<public-ip>Check the services
First boot takes under a minute while it creates the secrets, the database and the administrator. Afterwards, nginx, Strapi and the first-boot unit are all active:
systemctl is-active nginx strapi strapi-firstbootactive active activenginx serves ports 80 and 443, and Strapi listens on the loopback address only. The only open ports are therefore 22, 80 and 443. Port 53 on 127.0.0.53/54 is the system's local DNS resolver.
sudo ss -tlnH | awk '{print $4}' | sort -V0.0.0.0:22 0.0.0.0:80 0.0.0.0:443 127.0.0.1:1337 127.0.0.53%lo:53 127.0.0.54:53 [::]:22 [::]:80 [::]:443Check the Node.js, Strapi, SQLite and nginx versions:
/opt/node/bin/node --version cd /opt/strapi && for m in @strapi/strapi @strapi/admin better-sqlite3 sharp react; do printf '%s %s\n' "$m" "$(/opt/node/bin/node -p "require('./node_modules/$m/package.json').version")"; done /opt/node/bin/node -e "const D=require('/opt/strapi/node_modules/better-sqlite3'); console.log('SQLite', new D(':memory:').prepare('select sqlite_version() v').get().v)" nginx -v 2>&1v24.22.2 @strapi/strapi 5.60.0 @strapi/admin 5.60.0 better-sqlite3 12.11.1 sharp 0.35.5 react 18.3.1 SQLite 3.53.2 nginx version: nginx/1.24.0 (Ubuntu)Get your sign-in details
The credentials file holds the address Strapi is served on (your instance's public DNS name by default), the admin panel address, and the administrator's e-mail and password. All of these are generated on this instance. Show the file:
sudo cat /root/strapi-credentials.txtSTRAPI_URL=https://ec2-<public-ip-dashed>.<region>.compute.amazonaws.com/ STRAPI_ADMIN_URL=https://ec2-<public-ip-dashed>.<region>.compute.amazonaws.com/admin STRAPI_ADMIN_EMAIL=admin@example.com STRAPI_ADMIN_PASSWORD=<24 letters and digits>Strapi admin users sign in with an e-mail address. The administrator starts as admin@example.com. Once you have configured an e-mail provider (Step 9), change it to your own address under your profile (top left of the admin panel), because password reset mails go to that address.
Sign in to the admin panel
Open STRAPI_ADMIN_URL in your browser. The certificate is self-signed and generated on your instance, so your browser warns once. Compare the certificate's SHA-256 fingerprint with the one on the instance (sudo openssl x509 -in /etc/nginx/ssl/strapi.crt -noout -fingerprint -sha256) and continue. Alternatively, install a certificate from your own certificate authority (Step 9).
Sign in with STRAPI_ADMIN_EMAIL and STRAPI_ADMIN_PASSWORD. The home page greets the administrator and links to the Content Manager, the Content-Type Builder, the Media Library and the settings.
Manage media and content
Open the Media Library and choose Add assets to upload images, videos and documents. Strapi stores the files in /var/lib/strapi/uploads and serves them from https://<your host>/uploads/<name>.
Uploads are limited to 256 MB. Strapi refuses HTML, SVG and executable files, and nginx sends every other non-passive type as a download.
Content types. Strapi runs here in production mode (strapi start), where the Content-Type Builder is read-only. You can see the content types but not create or change them, which is how Strapi is designed to run in production. Model your content types in a Strapi development project on your workstation (with npm run develop), then deploy their code to this instance and rebuild the admin panel (Step 10). Entries, media, users, roles and settings are managed in the admin panel as usual.
The admin API works with the same account. For example, from a machine that trusts your certificate (or with -k while you experiment), sign in and read your own profile:
curl -s -H 'Content-Type: application/json' \
-d '{"email":"admin@example.com","password":"<your password>"}' https://<your host>/admin/login
curl -s -H 'Authorization: Bearer <data.token from the answer>' https://<your host>/admin/users/meYour sites and apps read content through the REST API with an API token (Settings, API Tokens) or with the Users & Permissions roles (Settings, Users & Permissions plugin, Roles).
Review the closed-by-default settings
The super administrator registration route is closed, public end-user sign-up is off, anonymous clients cannot list users, Enterprise Edition and Strapi AI are off, and Strapi carries no telemetry identity. Verify each setting:
H=$(sed -n 's/^set \$strapi_host "\(.*\)";$/\1/p' /etc/nginx/strapi-host.conf)
curl -sk --resolve "$H:443:127.0.0.1" -H 'Content-Type: application/json' -d '{"email":"someone@example.com","password":"Example12345678901234567","firstname":"Someone"}' "https://$H/admin/register-admin"; echo
curl -sk --resolve "$H:443:127.0.0.1" -H 'Content-Type: application/json' -d '{"username":"someone","email":"someone@example.com","password":"Example12345678901234567"}' "https://$H/api/auth/local/register"; echo
curl -sk -o /dev/null -w 'anonymous user list: %{http_code}\n' --resolve "$H:443:127.0.0.1" "https://$H/api/users"
curl -sk --resolve "$H:443:127.0.0.1" "https://$H/admin/project-type"; echo
curl -sk --resolve "$H:443:127.0.0.1" "https://$H/admin/init"; echo{"data":null,"error":{"status":400,"name":"ApplicationError","message":"You cannot register a new super admin","details":{}}} {"data":null,"error":{"status":400,"name":"ApplicationError","message":"Register action is currently disabled","details":{}}} anonymous user list: 403 {"data":{"isEE":false,"isTrial":false,"features":[],"flags":{"nps":false,"promoteEE":false,"docLinks":true},"ai":{"enabled":false}}} {"data":{"uuid":false,"hasAdmin":true,"menuLogo":null,"authLogo":null}}To let your app's users register themselves, choose the default role first. Then turn Enable sign-ups on under Settings, Users & Permissions plugin, Advanced settings.
Settings, Overview shows the Strapi and Node.js versions and the Community plan.
Locate your data and configuration
Strapi's data is the SQLite database /var/lib/strapi/data.db and the upload folder /var/lib/strapi/uploads. Its configuration is /etc/strapi/strapi.env, and its secrets are /etc/strapi/secrets.json. Inspect them:
sudo grep -v '^#' /etc/strapi/strapi.env | sed -E 's#https://[^/]+#https://<your host>#'
sudo stat -c '%n %U:%G %a' /root/strapi-credentials.txt /etc/strapi /etc/strapi/strapi.env /etc/strapi/secrets.json /opt/strapi /var/lib/strapi /var/lib/strapi/data.db /var/lib/strapi/uploads
sudo python3 -c 'import json; print(sorted(json.load(open("/etc/strapi/secrets.json"))))'DATABASE_FILENAME=/var/lib/strapi/data.db STRAPI_SECRETS_FILE=/etc/strapi/secrets.json STRAPI_PUBLIC_URL=https://<your host> /root/strapi-credentials.txt root:root 600 /etc/strapi root:strapi 750 /etc/strapi/strapi.env root:strapi 640 /etc/strapi/secrets.json root:strapi 640 /opt/strapi root:root 755 /var/lib/strapi strapi:strapi 750 /var/lib/strapi/data.db strapi:strapi 640 /var/lib/strapi/uploads strapi:strapi 750 ['ADMIN_JWT_SECRET', 'API_TOKEN_SALT', 'APP_KEYS', 'ENCRYPTION_KEY', 'JWT_SECRET', 'TRANSFER_TOKEN_SALT']To back up, take an EBS snapshot of the volume. Alternatively, copy the database consistently with SQLite's own backup command and copy the upload folder:
sudo -u strapi sqlite3 /var/lib/strapi/data.db ".backup '/var/lib/strapi/data-backup.db'"
sudo tar -C /var/lib/strapi -czf /root/strapi-uploads.tgz uploadsKeep /etc/strapi/secrets.json with your backups. ENCRYPTION_KEY decrypts the API tokens Strapi stores, and the JWT secrets sign every session.
To keep the data on a separate EBS volume, stop strapi, mount the volume at /var/lib/strapi (owner strapi:strapi, mode 0750), copy the files across and start strapi again. To move to PostgreSQL or MySQL, use Strapi's data export and import (strapi export / strapi import) with a project configured for that database.
Set your own DNS name, certificate and e-mail
Port 80 sends browsers to HTTPS, the health endpoint answers through Strapi for load balancer checks, and an unknown host name gets no answer. Verify this behavior:
H=$(sed -n 's/^set \$strapi_host "\(.*\)";$/\1/p' /etc/nginx/strapi-host.conf)
curl -s -o /dev/null -w 'port 80: %{http_code} -> %{redirect_url}\n' --resolve "$H:80:127.0.0.1" "http://$H/admin" | sed -E 's#https://[^/]+#https://<your host>#'
curl -sk -o /dev/null -w 'admin panel: %{http_code}\n' --resolve "$H:443:127.0.0.1" "https://$H/admin/auth/login"
curl -sk -o /dev/null -w 'health: %{http_code}\n' --resolve "$H:443:127.0.0.1" "https://$H/healthz"
curl -sk -o /dev/null -w 'unknown host name: %{http_code}\n' -H 'Host: example.com' https://127.0.0.1/ || trueport 80: 301 -> https://<your host>/admin admin panel: 200 health: 204 unknown host name: 000The status 000 means nginx closed the connection without an answer.
To serve Strapi under your own DNS name, first point an A record at the instance's Elastic IP. Then run:
sudo strapi-set-hostname cms.example.comThis generates a new certificate for that name, makes nginx answer it, sets Strapi's public URL and CORS origin to https://cms.example.com and restarts Strapi.
To use a certificate from your own certificate authority (for example one you obtain with certbot for your-domain), replace /etc/nginx/ssl/strapi.crt and /etc/nginx/ssl/strapi.key (root, 0600 for the key). Then run sudo systemctl reload nginx.
E-mail. The image's e-mail provider (/opt/strapi/sac/email-none.js) sends nothing, so password reset and confirmation mails are dropped. Only a line saying a message was not sent is logged. To send mail, add an e-mail provider such as @strapi/provider-email-nodemailer with your SMTP server to the project, set it in config/plugins.js, and rebuild (Step 10).
Change passwords, add content types and upgrade
Change the administrator's password with the command below. It writes a new random password to the credentials file, ends every session of that user and restarts Strapi.
sudo strapi-set-passwordFor another admin user, sudo strapi-set-password --email <user e-mail> prints the new password. With --stdin, the command reads a password from standard input instead. The password must be 24 to 72 letters and digits, with upper and lower case and a digit.
Adding content types, plugins or providers, or upgrading Strapi, means rebuilding the project. The image ships the Node.js runtime but not npm, so install the Node.js 24 toolchain with npm in a working copy. Then run:
cp -a /opt/strapi ~/strapi && cd ~/strapi npm ci
# copy your content types into src/api/ and src/components/, edit config/, or raise the @strapi packages in package.json python3 -c 'import base64,json,secrets; r=lambda: base64.b64encode(secrets.token_bytes(32)).decode(); print(json.dumps({"APP_KEYS":[r() for _ in range(4)], **{k: r() for k in ("API_TOKEN_SALT","ADMIN_JWT_SECRET","TRANSFER_TOKEN_SALT","JWT_SECRET","ENCRYPTION_KEY")}}))' > ~/build-secrets.json NODE_ENV=production STRAPI_SECRETS_FILE=$HOME/build-secrets.json DATABASE_FILENAME=/tmp/build.db STRAPI_PUBLIC_URL=https://<your host> npx strapi build rm -f ~/build-secrets.json /tmp/build.db
sudo systemctl stop strapi
sudo rsync -a --delete --exclude public/uploads ~/strapi/ /opt/strapi/ && sudo chown -R root:root /opt/strapi
sudo systemctl start strapiThe build needs a secrets file only to load the configuration, so it uses a throwaway one. The running service keeps reading /etc/strapi/secrets.json.
Strapi updates the database schema for new or changed content types when it starts. Back up the database first (Step 8) and test changes on a copy.
Security checks
Strapi runs as its own user with a read-only system and only its data folder writable, and instance metadata is blocked for Strapi and nginx. Verify the service settings:
systemctl show strapi -p User -p ProtectSystem -p ReadWritePaths -p NoNewPrivileges -p IPAddressDeny
systemctl show nginx -p IPAddressDenyIPAddressDeny=fd00:ec2::254/128 169.254.169.254/32 168.63.129.16/32 User=strapi ReadWritePaths=/var/lib/strapi ProtectSystem=strict NoNewPrivileges=yes IPAddressDeny=fd00:ec2::254/128 169.254.169.254/32 168.63.129.16/32The certificate generated on first boot is a server certificate, not a certificate authority. Confirm it:
sudo openssl x509 -in /etc/nginx/ssl/strapi.crt -noout -ext basicConstraints,extendedKeyUsage | sed 's/^ *//'X509v3 Basic Constraints: critical CA:FALSE X509v3 Extended Key Usage: TLS Web Server AuthenticationThe database holds exactly the administrator and no end users. Confirm it:
sudo -u strapi sqlite3 -readonly /var/lib/strapi/data.db 'SELECT email FROM admin_users;' 'SELECT count(*) FROM up_users;'admin@example.com 0Server components
The image contains the following components:
- Strapi project (code and admin build, root-owned): /opt/strapi
- Node.js 24.22.2 LTS: /opt/node/bin/node
- Strapi service: strapi.service (strapi start on 127.0.0.1:1337, user strapi)
- Web server: nginx.service, site /etc/nginx/sites-available/sac-strapi
- Data: /var/lib/strapi (SQLite data.db, uploads/)
- Configuration and secrets: /etc/strapi/strapi.env, /etc/strapi/secrets.json
- Helpers: strapi-set-hostname, strapi-set-password, strapi-cred-roundtrip
- Package list, license and Enterprise code note: /usr/share/doc/strapi/PACKAGES.txt, LICENSE, EE-CODE.txt
- Logs:
sudo journalctl -u strapi(Strapi) and/var/log/nginx/
Remote data transfer (strapi transfer --to https://<your host>/admin from another Strapi project, with a transfer token from Settings, Transfer Tokens) is served at /admin/transfer/runner/. It is the only WebSocket endpoint, and nginx refuses it from any browser on another site.
Security recommendations
- Restrict SSH. Allow port 22 in the security group from your own networks only.
- Protect the administrator account. Configure an e-mail provider (Step 9), then change the administrator e-mail to your own (Step 4). Keep the password secret, and rotate it if it may have been shared (Step 10).
- Use a trusted certificate. Set your own DNS name and a certificate your browsers trust (Step 9).
- Apply least privilege. Give admin users the narrowest role they need, and give each app its own API token with only the permissions it needs. Leave public sign-up off unless your app needs it.
- Stay current. Keep Strapi on a current release (Step 10) and keep the operating system patched. Unattended security upgrades are on, and
sudo apt-get update && sudo apt-get upgradeapplies everything else. - Know the Marketplace page behavior. The admin panel's Marketplace page loads the plugin list from Strapi's servers in the browser of whoever opens it.
Support and licensing
Strapi is free software under the MIT license for everything outside its ee/ directories (a copy is at /usr/share/doc/strapi/LICENSE). The ee/ directories ship dormant inside Strapi's npm packages. They are under the Strapi Enterprise license and are not enabled on this image (/usr/share/doc/strapi/EE-CODE.txt). Node.js, React and the other bundled packages carry their own open-source licenses, listed in /usr/share/doc/strapi/PACKAGES.txt. There is no per-user or per-CPU license fee and no license key.
One installed dependency, zod 4.4.3, which Strapi 5.60.0 pins, has a published advisory about memory use when validating very large arrays. On this image it is reachable only by signed-in admin users, request bodies are capped at 1 MB, and the image is rebuilt when Strapi moves to the fixed release.
The AMI price covers SecureAnyCloud's hardening, first-boot setup and support.
- Email: sales@secureanycloud.com
- Website: https://secureanycloud.com
- Support: 24/7, with a 24-hour response SLA
Deploy on AWS
Launch Strapi 5.60.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