Cockpit CMS 2.14.1
Overview
Cockpit CMS is an open-source, headless (API-first) content platform. You model content in the admin interface as collections, singletons and trees made of typed fields, manage images and files in the asset manager, and read everything back through the REST and GraphQL Content API. The same content can feed a website, a mobile app, a static site generator or several frontends at once. Cockpit stores content in a file-based store (MongoLite, on SQLite), so there is no database server to run.
This SecureAnyCloud image runs Cockpit CMS 2.14.1 on Ubuntu 24.04 LTS on Amazon EC2. It is one version of the SecureAnyCloud BookStack, CMS, Blogs and Wikis listing on AWS Marketplace. You select it as the software version when you subscribe and launch.
What you get:
- No web installer, no default login. Upstream Cockpit ships an
/installpage that creates the first administrator for whoever opens it while the system has no user. This image does not ship the installer, and nginx also refuses/install. On first boot, before the web server starts, the instance creates anadminadministrator with a password generated on your instance (stored only as a bcrypt hash) and a security key of its own. The password is recorded in/root/cockpit-credentials.txt. No credentials from the image build ship in the image. - A security key unique to your instance. Cockpit signs its sign-in and CSRF tokens and encrypts stored data with a
sec-key. Upstream's default key is the same published value on every installation that does not change it. On this image, first boot writes a random 256-bit key into/var/www/cockpit/config/config.php. - The current release. Cockpit 2.14.1 is the newest stable release. It fixes the security issues reported against 2.14.0 and earlier: missing access checks and path traversal in the bucket file API, command injection through video file names, stored cross-site scripting in Set fields, and a login timing leak. The download is verified against a pinned checksum.
- HTTPS from the first boot. nginx serves Cockpit on port 443 with a certificate generated on your instance's first boot. Port 80 redirects to HTTPS.
- Fail closed. nginx and PHP refuse to start until first boot has finished.
- Only Cockpit's own front controller runs. Every request that is not a plain asset goes through Cockpit's router. No file in the installation (configuration, libraries, content store, caches or logs) is served or run directly. Uploaded files and thumbnails are served as static files only and never run as PHP. A sandboxing content security policy prevents an uploaded SVG or HTML file from running script in your admin session.
- A rate-limited login. Cockpit does not throttle sign-in attempts itself. On this image, nginx limits sign-in, two-factor and password reset requests to 6 a minute from one address after a burst of 10. The address comes from the connection, never from a header a client sends, and Cockpit's login log records the same real address.
- A Content API that is closed until you open it. No API key exists on a new instance, so the REST and GraphQL API refuses every request until you create a key (Step 8). A small SecureAnyCloud addon also switches off Cockpit's optional ?rspc API response cache. In 2.14.1 that cache answers a request before it checks the API key (fixed upstream after the 2.14.1 release).
- Hardened defaults. The Cockpit code is owned by root and read-only to PHP. PHP can write only Cockpit's storage, cannot run shell commands or open URLs as files, and is confined to Cockpit's folders. nginx and PHP are blocked from the instance metadata service, and the AMI requires IMDSv2. Requests for a host name the instance does not serve are dropped, and Cockpit's links and API addresses always use the host name you configure, never the one a request claims.
- A patched base. Ubuntu 24.04 LTS is fully patched at build time, with unattended security upgrades switched on.
The image contains Cockpit CMS 2.14.1 (the upstream core release with its bundled dependencies, checksum verified), PHP 8.3 FPM, nginx, Ubuntu 24.04 LTS, cloud-init and the AWS Systems Manager agent. Cockpit's storage (content store, uploads, thumbnails, cache and logs) sits on a separate 20 GiB EBS volume mounted at /var/www/cockpit/storage.
Default user: ubuntu. Architecture: x86_64. 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 (Cockpit and its API) and TCP 80 (redirect to HTTPS) from the networks that should reach them. Restrict port 22 to your own networks. Open port 443 to your editors and to the frontends that read the API.
Launch the instance
In the AWS Marketplace listing, choose Continue to Subscribe, then Continue to Configuration. Pick Cockpit CMS 2.14.1 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. Cockpit is light, so a smaller instance is fine for a small team. Keep the second volume (Cockpit's storage) in the launch settings. You can make it larger there.
Alternatively, launch with the AWS CLI. 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=cockpit}]'Connect to your instance
Connect over SSH as the ubuntu user. This is the default user for Cockpit CMS 2.14.1 on Ubuntu 24.04.
ssh -i <your-key.pem> ubuntu@<public-ip>Check the services
First boot takes a few seconds. When it finishes, nginx, PHP and the first-boot unit are all active:
systemctl is-active nginx php8.3-fpm cockpit-firstbootactive active activenginx serves ports 80 and 443. PHP listens on a local socket only, and there is no database server, so the only open ports are 22, 80 and 443:
sudo ss -tlnp | awk 'NR==1 || /nginx|sshd|php/' | awk '{print $1, $4, $6}' | sed -E 's/,pid=[0-9]+,fd=[0-9]+//g'State Local Peer LISTEN 0.0.0.0:443 users:(("nginx"),("nginx"),("nginx")) LISTEN 0.0.0.0:80 users:(("nginx"),("nginx"),("nginx")) LISTEN 0.0.0.0:22 users:(("sshd"),("systemd")) LISTEN [::]:443 users:(("nginx"),("nginx"),("nginx")) LISTEN [::]:80 users:(("nginx"),("nginx"),("nginx")) LISTEN [::]:22 users:(("sshd"),("systemd"))Check the Cockpit release, the PHP version and the storage volume:
grep "^const APP_VERSION" /var/www/cockpit/bootstrap.php php -v | head -1 findmnt -no TARGET,FSTYPE,SIZE /var/www/cockpit/storageconst APP_VERSION = '2.14.1'; PHP 8.3.6 (cli) (built: Oct 19 2026 15:16:58) (NTS) /var/www/cockpit/storage ext4 19.5GGet your sign-in details
The credentials file holds the address Cockpit is served on (your instance's public DNS name by default), the administrator user name and the password generated on this instance. Display it with:
sudo cat /root/cockpit-credentials.txtCOCKPIT_URL=https://ec2-<public-ip-dashed>.<region>.compute.amazonaws.com/ COCKPIT_ADMIN_USER=admin COCKPIT_ADMIN_PASSWORD=<24 letters and digits>Sign in
Open COCKPIT_URL in your browser. The certificate is self-signed and generated on your instance, so accept the browser warning once, or install your own certificate (Step 12). Sign in as admin with COCKPIT_ADMIN_PASSWORD.
The dashboard shows the latest content and assets. The icons on the left open Content, Assets, API & Security and search. The icon at the bottom left opens all Settings.
Open your account from the avatar at the top right. There you can change the administrator's email address (it starts as admin@localhost.localdomain) and password, and switch on two-factor authentication.
Model your content
Under Content, create a model for each kind of content you publish:
- A collection holds many entries of one shape, such as blog posts, products or team members.
- A singleton holds one-off content, such as the site settings or the home page.
- A tree holds nested entries, such as a navigation menu or documentation pages.
Give the model a name. It becomes the name in the API, for example posts. Add typed fields: text, rich text, numbers, dates, booleans, assets, links to other models, sets and repeaters. Save the model, then add entries with Create item.
Assets
Assets holds the images, videos and documents your content uses. Upload files there, or directly from an asset field. Organize them in folders and edit their titles and alt texts. Cockpit generates image thumbnails itself.
Uploaded files live on the storage volume under /var/www/cockpit/storage/uploads.
Read your content through the API
A new instance has no API key, so the Content API refuses every request. Confirm it:
H=$(sed -n 's/^set \$cockpit_host "\(.*\)";$/\1/p' /etc/nginx/cockpit-host.conf)
curl -sk -w '\n' --resolve "$H:443:127.0.0.1" "https://$H/api/content/items/posts"{"error":"Authentication failed"}To open the API, go to API & Security (the API icon on the left), choose Add key and give the key a role. A key with the built-in admin role can do everything. For a public website, create a role under Settings > Users > Manage roles that can only read the models the site shows, and give the key that role.
Access > Configure on the same page sets what requests without a key may read. Nothing is allowed on a new instance. The page also shows your REST and GraphQL endpoints, which always use the host name you configured.
Send the key in the api-key header:
curl -sk -H 'api-key: <API_KEY>' 'https://<your-host>/api/content/items/posts'
curl -sk -H 'api-key: <API_KEY>' -H 'Content-Type: application/json' \
-d '{"query":"{ postsModel { title } }"}' 'https://<your-host>/api/gql'Keep keys out of browser code unless the key's role only reads public content.
This image switches off Cockpit's optional ?rspc=1 response cache with the root-owned addon /var/www/cockpit/addons/SacGuard (see /usr/share/doc/cockpit/SAC-GUARD.txt). In 2.14.1, a cached response is answered before the API key is checked. The fix is in Cockpit's development branch. Remove the addon once you install a release that includes it.
Users and roles
Use Settings > Users to add editors and other administrators. Manage roles on the same page defines what each role may do: which models it can read, create, edit or publish, and whether it can manage assets. Give each person their own account with the narrowest role that works.
Email: Cockpit sends password reset and magic sign-in links by email. The image has no mail server. Configure a mailer (SMTP) in config/config.php (Cockpit's mailer setting) before you rely on these links. You can always reset the administrator's password from the shell (Step 13).
Your Cockpit storage
Cockpit writes everything it stores to its own EBS volume at /var/www/cockpit/storage, readable only by the web server. The folders are:
dataholds the content store:app.sqlitewith your models and entries,system.sqlitewith users, roles, API keys and the log, andapp.memory.sqlitewith caches.uploadsholds your assets.tmpholds the thumbnails.cacheandlogshold working files.
The per-instance configuration is /var/www/cockpit/config/config.php. Everything else belongs to root:
sudo ls -l /var/www/cockpit/storage /var/www/cockpit/storage/data | sed -E 's/ +/ /g' | cut -d' ' -f1,3,4,9-/var/www/cockpit/storage: total drwxr-x--- www-data www-data cache drwxr-x--- www-data www-data data drwxr-x--- www-data www-data logs drwx------ root root lost+found drwxr-x--- www-data www-data tmp drwxr-x--- www-data www-data uploads /var/www/cockpit/storage/data: total -rw-r--r-- www-data www-data app.memory.sqlite -rw-r--r-- www-data www-data app.sqlite -rw-r--r-- www-data www-data system.sqliteBack up Cockpit with an EBS snapshot of the storage volume (and of the root volume for the configuration). Alternatively, stop the services and back up the files:
sudo systemctl stop nginx php8.3-fpm
sudo tar -czf cockpit-backup.tar.gz -C / var/www/cockpit/storage var/www/cockpit/config/config.php
sudo systemctl start php8.3-fpm nginxTo restore onto a new instance, let its first boot finish, then stop nginx and PHP. Replace the contents of /var/www/cockpit/storage with your copy, owned by www-data, and leave lost+found alone. Replace config/config.php with your copy, owned by root:www-data with mode 0640. Its sec-key must match the store. Then start the services again. The restored store brings its own users and passwords.
Security checks
Confirm that port 80 redirects browsers to HTTPS, that the health endpoint answers load balancer checks, and that the installer, the configuration, the content store, the module cache and Cockpit's code are not reachable:
H=$(sed -n 's/^set \$cockpit_host "\(.*\)";$/\1/p' /etc/nginx/cockpit-host.conf)
curl -s -o /dev/null -w 'port 80: %{http_code} -> %{redirect_url}\n' --resolve "$H:80:127.0.0.1" "http://$H/auth/login"
curl -sk --resolve "$H:443:127.0.0.1" "https://$H/healthz" for p in /install/ /config/config.php /storage/data/system.sqlite /storage/cache/modules.cache.php /lib/vendor/autoload.php /modules/App/bootstrap.php /bootstrap.php /cron.php; do
curl -sk -o /dev/null -w "$p: %{http_code}\n" --resolve "$H:443:127.0.0.1" "https://$H$p" done
curl -s -o /dev/null -w 'unknown host name: %{http_code}\n' -H 'Host: example.com' http://127.0.0.1/ || trueport 80: 301 -> https://ec2-<public-ip-dashed>.<region>.compute.amazonaws.com/auth/login ok /install/: 404 /config/config.php: 404 /storage/data/system.sqlite: 404 /storage/cache/modules.cache.php: 404 /lib/vendor/autoload.php: 404 /modules/App/bootstrap.php: 404 /bootstrap.php: 404 /cron.php: 404 unknown host name: 000A 000 result means nginx closed the connection without an answer. Next, check Cockpit's own view. It should show one account, admin, with no personal API key, no API keys at all, the configured HTTPS address as its site URL, and debug mode off:
sudo -u www-data php /usr/local/lib/sac/cockpit/cockpit-admin.php summaryusers=admin:admin:nokey apikeys=0 site_url=https://ec2-<public-ip-dashed>.<region>.compute.amazonaws.com debug=off version=2.14.1Check file permissions. The credentials file is readable by root only, the configuration by root and the web server's group, and the storage by the web server only. The code belongs to root:
sudo stat -c '%a %U:%G %n' /root/cockpit-credentials.txt /var/www/cockpit/config/config.php /var/www/cockpit/storage /var/www/cockpit/index.php600 root:root /root/cockpit-credentials.txt 640 root:www-data /var/www/cockpit/config/config.php 750 www-data:www-data /var/www/cockpit/storage 644 root:root /var/www/cockpit/index.phpCheck the certificate. It was generated on this instance and names the instance's own addresses:
H=$(sed -n 's/^set \$cockpit_host "\(.*\)";$/\1/p' /etc/nginx/cockpit-host.conf)
echo | openssl s_client -connect 127.0.0.1:443 -servername "$H" 2>/dev/null | openssl x509 -noout -subject -ext subjectAltNamesubject=CN = ec2-<public-ip-dashed>.<region>.compute.amazonaws.com, O = sac X509v3 Subject Alternative Name: DNS:ec2-<public-ip-dashed>.<region>.compute.amazonaws.com, IP Address:<public-ip>, IP Address:<private-ip>, IP Address:127.0.0.1, DNS:localhostFinally, confirm the process hardening. nginx and PHP cannot reach the instance metadata service. PHP cannot run shell commands or open URLs as files, and it is confined to Cockpit's folders. nginx hands PHP the configured host name and the real client address from the connection, never values a client sends, and it rate-limits sign-in requests:
systemctl show nginx -p IPAddressDeny
systemctl show php8.3-fpm -p IPAddressDeny php-fpm8.3 -i 2>/dev/null | grep -E '^(open_basedir|disable_functions|expose_php|allow_url_fopen) ' | cut -d' ' -f1-3 grep -E 'fastcgi_param (HTTP_HOST|SERVER_NAME|HTTP_X_FORWARDED_FOR|HTTP_CLIENT_IP) ' /etc/nginx/snippets/sac-cockpit-php.conf grep -h 'limit_req' /etc/nginx/conf.d/sac-cockpit-login-guard.conf /etc/nginx/sites-available/sac-cockpitIPAddressDeny=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 allow_url_fopen => Off disable_functions => exec,passthru,shell_exec,system,proc_open,popen,pcntl_exec expose_php => Off open_basedir => /var/www/cockpit/:/var/lib/php/sessions/:/tmp/ fastcgi_param HTTP_HOST $cockpit_host; fastcgi_param SERVER_NAME $cockpit_host; fastcgi_param HTTP_X_FORWARDED_FOR $remote_addr; fastcgi_param HTTP_CLIENT_IP $remote_addr; limit_req_zone $cockpit_auth_key zone=cockpit_auth:10m rate=6r/m; limit_req_status 429; limit_req zone=cockpit_auth burst=10 nodelay;If a burst of wrong passwords rate-limits you (HTTP 429), wait a minute and try again. The limit only slows repeated attempts from one address.
Your own DNS name and certificate
Point a DNS record at the instance, then set the name Cockpit is served on. The command below creates a new self-signed certificate for the name. It also updates nginx, Cockpit's site_url (every absolute link and API address Cockpit shows, in config/config.php) and the URL in /root/cockpit-credentials.txt. The name persists across reboots.
sudo cockpit-set-hostname <your-domain>To use a certificate from your own certificate authority instead, copy it over the generated pair and reload nginx. Running cockpit-set-hostname again replaces it with a self-signed certificate.
sudo install -m 0644 <your-domain>.crt /etc/nginx/ssl/cockpit.crt
sudo install -m 0600 <your-domain>.key /etc/nginx/ssl/cockpit.key
sudo nginx -t && sudo systemctl reload nginxOnce browsers trust the certificate, you can add an HSTS header (add_header Strict-Transport-Security "max-age=31536000" always;) to /etc/nginx/snippets/sac-cockpit-headers.conf. The image does not send one, because browsers ignore HSTS from a site whose certificate they do not trust.
Passwords, extensions and upgrades
Run cockpit-set-password to give admin a new random password and record it in /root/cockpit-credentials.txt. Run it without arguments, or pass your own password (24 to 96 letters and digits) on stdin. Other users change their own password under Account. Administrators can set any user's password in Settings > Users.
sudo cockpit-set-password printf '%s\n' '<your-new-password>' | sudo cockpit-set-password --stdinFeatures that need a writable code tree. Cockpit's built-in Updater and Spaces (extra Cockpit instances inside one installation) write into the Cockpit code folder. On this image that folder belongs to root, so both stop with an error instead of changing the code from the browser.
The Finder file manager works, except its "unzip" action, which needs PHP's zip extension. Add the extension if you use that action. It comes from Ubuntu's universe repository:
sudo apt-get install php8.3-zip && sudo systemctl restart php8.3-fpmUpgrades. Snapshot the instance first. Download the new core release (cockpit-core.zip) from Cockpit's GitHub releases, compare its SHA-256 checksum with the one on the release page, and read the release notes. Then replace the code as root, keeping config and storage:
sudo apt-get install unzip
sudo systemctl stop nginx php8.3-fpm unzip -q cockpit-core.zip -d /tmp/cockpit-new cd /var/www/cockpit && sudo rm -rf index.php bootstrap.php cron.php favicon.png robots.txt lib modules
sudo cp -a /tmp/cockpit-new/cockpit-core/{index.php,bootstrap.php,cron.php,favicon.png,robots.txt,lib,modules} /var/www/cockpit/
sudo chown -R root:root /var/www/cockpit/{index.php,bootstrap.php,cron.php,favicon.png,robots.txt,lib,modules}
sudo rm -f /var/www/cockpit/storage/cache/modules.cache.php
sudo systemctl start php8.3-fpm nginxDo not copy the release's install folder into the web root. Your content in /var/www/cockpit/storage is not touched.
Server components
These are the main components and where they live on the instance:
- Cockpit code (root-owned): /var/www/cockpit
- Storage (content store, uploads, thumbnails, cache, logs): /var/www/cockpit/storage, on its own EBS volume
- Per-instance configuration (sec-key, site URL): /var/www/cockpit/config/config.php
- SecureAnyCloud addon (switches off the ?rspc cache): /var/www/cockpit/addons/SacGuard
- nginx site and login rate limit: /etc/nginx/sites-available/sac-cockpit and /etc/nginx/conf.d/sac-cockpit-login-guard.conf
- PHP settings: /etc/php/8.3/fpm/conf.d/99-sac-cockpit.ini
- Certificate and key: /etc/nginx/ssl/
- First-boot script: /usr/local/sbin/cockpit-firstboot.sh
- Helpers: /usr/local/sbin/cockpit-set-hostname and cockpit-set-password
- License, changelog and release notes: /usr/share/doc/cockpit/
- Credentials: /root/cockpit-credentials.txt (mode 0600)
Security recommendations
- Allow port 22 in the security group from your own networks only. Allow port 443 only from the networks that edit content or read the API.
- Keep the administrator password secret and rotate it if it may have been shared (Step 13). Give each person their own account with the narrowest role, and switch on two-factor authentication for administrators.
- Give API keys the narrowest role that works. Never put an admin-role key in a browser or mobile app.
- Use your own DNS name and a certificate your browsers trust (Step 12).
- Keep
config/config.phpsecret. Itssec-keysigns sign-in tokens. - Keep the operating system patched. Unattended security upgrades are on, and
sudo apt-get update && sudo apt-get upgradeapplies everything else. PHP-FPM comes from Ubuntu's universe repository. Runsudo pro security-status --esm-appsto see whether Ubuntu Pro (ESM Apps) has security updates for it. None were pending when the image was built. - Snapshot the storage volume regularly (Step 10).
Support and licensing
Cockpit CMS is open source under the MIT License. A copy is at /usr/share/doc/cockpit/LICENSE. There is no per-user or per-CPU license fee. This image installs the unmodified upstream core release, without its web installer and without one bundled library's README (it carries a sample private key), plus the small SecureAnyCloud addon described in Step 8. 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 Cockpit CMS 2.14.1 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