Applications AWS

Umbraco 17.8.0 on AWS User Guide

| Product: Umbraco 17.8.0 on Ubuntu 24.04 on AWS

Overview

This guide covers launching and using the Umbraco 17.8.0 on Ubuntu 24.04 AMI from cloudimg on Amazon EC2. It is one version of the cloudimg BookStack, CMS, Blogs and Wikis listing on AWS Marketplace; you pick it as the software version when you subscribe and launch.

Umbraco is a free and open source content management system for .NET. Editors build pages from document types and templates, write in a rich-text editor, manage media, members, languages and translations, and schedule publishing; developers extend it with C# and Razor views, and the Content Delivery API serves content to any front end. Umbraco 17 is a long-term support (LTS) release on .NET 10.

The cloudimg image adds:

  • No reachable installer, no shared database. A fresh Umbraco opens its installation wizard to whoever visits first. This image ships no database, no data protection keys and no administrator. Your instance's first boot generates the administrator password and runs Umbraco's own unattended install before the web server starts, as the unprivileged umbraco user on a temporary local port; the installer process is then stopped and checked to be gone, and the install API is refused by nginx.
  • Secrets of your instance's own, never on a command line. The administrator password, the ASP.NET Core data protection keys that protect back-office sign in, the site id, the key that signs image URLs and the TLS key are generated on your instance; nothing from the image build is reused. The password reaches the installer through its environment only, never as a command line argument, and is never logged.
  • A fixed rich-text editor. Umbraco 17.8.0's back office bundles prosemirror-view 1.42.2, which has a high severity cross-site scripting flaw in its paste handling (GHSA-c8x8-7fp4-3x9w, fixed in 1.42.3). This image rebuilds Umbraco's back office from the official 17.8.0 source with that one library raised to 1.42.3 and nothing else changed (Step 13 shows how to check it).
  • A patched runtime. Umbraco runs on the ASP.NET Core 10.0.14 runtime from Ubuntu's main archive, the October 2026 .NET security release. The site's NuGet packages are restored from a lock file that pins every package by its content hash.
  • HTTPS from the first boot. nginx serves Umbraco on port 443 with a certificate generated on your instance's first boot. Port 80 redirects to HTTPS, and the back office (which needs a secure connection for its sign in) works straight away.
  • Fail closed. nginx and Umbraco do not start until first boot has finished; if first boot cannot complete, the site stays offline rather than open.
  • A rate-limited sign in. nginx allows 10 attempts a minute from one address (after a burst of 10) at the back-office sign in, two-factor, token, password reset and invitation routes, in every spelling Umbraco accepts, using the address of the connection, never a header a client sends. The administrator password is 25 random characters, and Umbraco locks an account for 15 minutes after 5 failed attempts.
  • Requests from other sites cannot act for you. Every request that changes something (any method but GET) and every WebSocket connection must prove it comes from your site's own address; anything else is refused before it reaches Umbraco.
  • The real host and client address. nginx hands Umbraco the host name you configure and nothing a request claims, so password reset and invitation links use your host name, and Umbraco's audit log records the address of the connection.
  • Hardened defaults. Umbraco's web server (Kestrel) listens on the loopback interface only, behind nginx; it runs as the umbraco user with systemd's sandboxing, and the application files are read-only to it: everything Umbraco writes (the database, media, templates, stylesheets, scripts, logs and indexes) lives under /var/lib/umbraco. Umbraco, nginx 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.

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 (Umbraco) and TCP 80 (redirects to HTTPS) from the networks that should reach them. Keep 22 to your own networks. Open 443 to your visitors for a public site, or only to your team while you build it.

Step 1: Launch the instance

In the AWS Marketplace listing choose Continue to Subscribe, then Continue to Configuration, pick Umbraco 17.8.0 on Ubuntu 24.04 as the software version and your Region, then Launch through EC2. Choose m5.large, your key pair and a security group as above.

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=umbraco}]'

Step 2: Connect to your instance

Version SSH user
Umbraco 17.8.0 on Ubuntu 24.04 ubuntu
ssh -i <your-key.pem> ubuntu@<public-ip>

Step 3: Check the services

First boot takes about a minute (Umbraco's installer creates the database). Then Umbraco and nginx are active, and the first boot unit is enabled:

systemctl is-active umbraco nginx
systemctl is-enabled umbraco nginx umbraco-firstboot
active
active
enabled
enabled
enabled

Only SSH and nginx listen on the network; Umbraco's Kestrel server listens on the loopback interface only:

sudo ss -lntp | awk 'NR==1 || /:22 |:80 |:443 |:5000 /' | sed -E 's/users:\(\("([a-z]+)".*/\1/'
State  Recv-Q Send-Q Local Address:Port Peer Address:PortProcess
LISTEN 0      511          0.0.0.0:443       0.0.0.0:*    nginx
LISTEN 0      511          0.0.0.0:80        0.0.0.0:*    nginx
LISTEN 0      4096         0.0.0.0:22        0.0.0.0:*    sshd
LISTEN 0      512        127.0.0.1:5000      0.0.0.0:*    dotnet
LISTEN 0      511             [::]:443          [::]:*    nginx
LISTEN 0      511             [::]:80           [::]:*    nginx
LISTEN 0      4096            [::]:22           [::]:*    sshd

The runtime and the release:

dotnet --list-runtimes
head -1 /usr/share/doc/umbraco/RELEASE
Microsoft.AspNetCore.App 10.0.14 [/usr/lib/dotnet/shared/Microsoft.AspNetCore.App]
Microsoft.NETCore.App 10.0.14 [/usr/lib/dotnet/shared/Microsoft.NETCore.App]
Umbraco CMS 17.8.0 (MIT, LICENSE.md at tag release-17.8.0, sha256 e15305002597b35d0afa0ca3a218f2ee7ba171b98be06649d1a2a2fb4069c5f4)

Step 4: Get your sign-in details

The credentials file holds the address Umbraco is served as (your instance's public DNS name by default), the back-office address, the administrator's e-mail and its password, all generated on this instance. Show it with:

sudo cat /root/umbraco-credentials.txt
UMBRACO_URL=https://ec2-<public-ip-dashed>.<region>.compute.amazonaws.com/
UMBRACO_BACKOFFICE_URL=https://ec2-<public-ip-dashed>.<region>.compute.amazonaws.com/umbraco
UMBRACO_ADMIN_EMAIL=admin@example.com
UMBRACO_ADMIN_PASSWORD=<12 letters and digits>-<12 letters and digits>

This is the only back-office user, a member of Umbraco's Administrators group. Its e-mail starts as admin@example.com; change it to your own address (top-right user menu, Edit) and configure SMTP (Step 11) before you rely on password reset or invitation e-mails.

Step 5: Sign in to the back office

Open UMBRACO_BACKOFFICE_URL in a browser. The certificate was generated on your instance, so the browser warns that it does not know the issuer; compare its SHA-256 fingerprint with the one in Step 10 and continue (or install your own certificate, Step 10). Sign in with the e-mail address and password from Step 4:

The Umbraco back-office sign in with the e-mail and password fields

The Content section opens. A new site has no content yet: create document types and templates in Settings, then content in Content:

The Umbraco back office Content section

Step 6: Create and edit content

In Settings > Document Types create a document type (for example Page) with a Rich Text Editor property, allow it at the root and let Umbraco create its template. Then in Content create a page of that type. The rich-text editor has a formatting toolbar, links, media and an HTML source view; text you type and text you paste from other documents keeps its formatting:

Editing a page in Umbraco's rich-text editor with typed and pasted text

Save and publish makes the page live at your site's address:

The page after Save and publish

Templates, partial views, stylesheets and scripts you edit in Settings are saved under /var/lib/umbraco (Step 7). Upload images and files in Media, add editors in Users, and website members in Members.

Step 7: Your data

Umbraco keeps everything it writes under /var/lib/umbraco; the application in /opt/umbraco/app is read-only to it and reaches those folders through links:

sudo ls -la /var/lib/umbraco | sed 1d
ls -l /opt/umbraco/app /opt/umbraco/app/wwwroot | grep -- '->' | awk '{print $9, $10, $11}'
drwxr-x---  9 umbraco umbraco 4096 Nov  1 03:09 .
drwxr-xr-x 49 root    root    4096 Nov  1 03:04 ..
drwxr-x---  2 umbraco umbraco 4096 Nov  1 03:16 css
drwxr-x---  2 umbraco umbraco 4096 Nov  1 03:16 data
drwx------  2 umbraco umbraco 4096 Nov  1 03:16 dp-keys
drwxr-x---  2 umbraco umbraco 4096 Nov  1 03:16 media
drwxr-x---  2 umbraco umbraco 4096 Nov  1 03:16 scripts
drwxr-x---  4 umbraco umbraco 4096 Nov  1 03:16 umbraco-runtime
drwxr-x---  3 umbraco umbraco 4096 Nov  1 03:19 views
Views -> /var/lib/umbraco/views
umbraco -> /var/lib/umbraco/umbraco-runtime
css -> /var/lib/umbraco/css
media -> /var/lib/umbraco/media
scripts -> /var/lib/umbraco/scripts
  • data/Umbraco.sqlite.db: content, document types, users, members and settings (SQLite).
  • dp-keys: the keys that protect back-office sign in. Back them up with the database; without them every user has to sign in again.
  • media, views, css, scripts: uploaded media and the templates, partial views, stylesheets and scripts edited in the back office.
  • umbraco-runtime: Umbraco's logs (Logs), search indexes and generated models (Data/TEMP).
  • /etc/umbraco/appsettings.Instance.json: this instance's site id, the key that signs image URLs and the application URL (root-owned, readable by Umbraco).

For a consistent backup stop Umbraco, copy /var/lib/umbraco and /etc/umbraco, and start it again (sudo systemctl stop umbraco, then sudo systemctl start umbraco), or take an EBS snapshot of the volume. Umbraco can also use SQL Server instead of SQLite (see Umbraco's documentation); this image is configured for SQLite.

Step 8: Settings

Umbraco reads /opt/umbraco/app/appsettings.json, then /opt/umbraco/app/appsettings.Production.json (this image's settings: the database, Kestrel on loopback, telemetry at Umbraco's lowest level, keep-alive self-ping off, the lockout time), then /etc/umbraco/appsettings.Instance.json. Add your own settings to appsettings.Production.json (for example SMTP, Step 11) and restart Umbraco:

sudo nano /opt/umbraco/app/appsettings.Production.json
sudo systemctl restart umbraco

Umbraco reports its version and the site id to telemetry.umbraco.com once a day at the Minimal level, and the Content dashboard shows Umbraco's news from news-dashboard.umbraco.com; change the telemetry level in Settings > Telemetry Data.

Step 9: Behind a proxy or load balancer

nginx hands Umbraco the configured host name, https and the address of the connection, and drops every forwarding header a client sends. Behind an Application Load Balancer, terminate TLS on the load balancer, forward to port 443 on the instance and set the instance's host name to the name your users browse to (Step 10). Umbraco then records the load balancer's address as the client; to log the visitor's address instead, use nginx's real_ip module with set_real_ip_from limited to your load balancer's subnets.

API clients that send state-changing requests to the Management API (for example an API user with client credentials) must send the header Origin: https://<your host name>; requests without proof that they come from your site are refused with 403.

Step 10: Security checks and your own DNS name

Port 80 redirects to HTTPS, and a request for any other host name is dropped (curl reports 000). Each block reads your host name from the credentials file first:

H="$(sudo sed -n 's#^UMBRACO_URL=https://\([^/]*\)/$#\1#p' /root/umbraco-credentials.txt)"
curl -s -o /dev/null -w '%{http_code} %{redirect_url}\n' "http://${H}/umbraco"
curl -sk -o /dev/null -w '%{http_code}\n' -H 'Host: evil.example' https://127.0.0.1/ || true
301 https://ec2-<public-ip-dashed>.<region>.compute.amazonaws.com/umbraco
000

The certificate is an end-entity certificate for your host name (it cannot sign other certificates). Compare its fingerprint with the one your browser shows:

sudo openssl x509 -in /etc/nginx/ssl/umbraco.crt -noout -subject -ext basicConstraints,extendedKeyUsage -fingerprint -sha256
subject=CN = ec2-<public-ip-dashed>.<region>.compute.amazonaws.com, O = cloudimg
X509v3 Basic Constraints: critical
    CA:FALSE
X509v3 Extended Key Usage: 
    TLS Web Server Authentication
sha256 Fingerprint=<your instance's fingerprint>

Kestrel cannot be reached on the instance's own network address, and Umbraco, nginx and first boot are blocked from the instance metadata service:

curl -s -m 5 -o /dev/null -w '%{http_code}\n' "http://$(hostname -I | awk '{print $1}'):5000/" || true
systemctl show -p IPAddressDeny umbraco nginx umbraco-firstboot
000
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/32

IPAddressDeny=169.254.169.254/32 fd00:ec2::254/128 168.63.129.16/32

A request from another site that changes something, and one with no proof of where it comes from, are refused by nginx (403); the install API is not served (404):

H="$(sudo sed -n 's#^UMBRACO_URL=https://\([^/]*\)/$#\1#p' /root/umbraco-credentials.txt)"
curl -sk --resolve "${H}:443:127.0.0.1" -o /dev/null -w '%{http_code}\n' -H 'Origin: https://evil.example' -H 'Content-Type: application/json' -d '{}' "https://${H}/umbraco/management/api/v1/security/back-office/login"
curl -sk --resolve "${H}:443:127.0.0.1" -o /dev/null -w '%{http_code}\n' -H 'Content-Type: application/json' -d '{}' "https://${H}/umbraco/management/api/v1/security/back-office/login"
curl -sk --resolve "${H}:443:127.0.0.1" -o /dev/null -w '%{http_code}\n' "https://${H}/umbraco/management/api/v1/install/settings"
403
403
404

The sign-in limit applies per network address. From the instance's own network address (loopback traffic is not limited), the first attempts reach Umbraco (401: wrong credentials) and the rest are refused with 429:

H="$(sudo sed -n 's#^UMBRACO_URL=https://\([^/]*\)/$#\1#p' /root/umbraco-credentials.txt)"
P="$(hostname -I | awk '{print $1}')"
for i in $(seq 1 14); do curl -sk -o /dev/null -w '%{http_code} ' --interface "${P}" --resolve "${H}:443:${P}" -H "Origin: https://${H}" -H 'Content-Type: application/json' -d '{"username":"nobody@example.com","password":"x"}' "https://${H}/umbraco/management/api/v1/security/back-office/login"; done; echo
401 401 401 401 401 401 401 401 401 401 401 429 429 429

Your own DNS name. Point a DNS name at the instance's public address (an Elastic IP keeps it stable), then make it the name Umbraco is served as. The helper mints a new certificate for it, pins nginx and the request guards to it, sets Umbraco's application URL and restarts Umbraco (the instance's IP addresses then redirect to the name):

sudo umbraco-set-hostname www.example.com

To use a certificate from a public authority (for example from Let's Encrypt with certbot's webroot or DNS method), replace /etc/nginx/ssl/umbraco.crt and /etc/nginx/ssl/umbraco.key with your full chain and key and reload nginx (sudo nginx -t && sudo systemctl reload nginx). With a trusted certificate in place you can add Strict-Transport-Security in /etc/nginx/snippets/cloudimg-umbraco-headers.conf (it is left out while the certificate is self-signed).

Step 11: Passwords, e-mail, upgrades and packages

  • Password: change the administrator password in the top-right user menu, and keep /root/umbraco-credentials.txt up to date or delete it. Add a user for each editor in Users rather than sharing the administrator.
  • E-mail: add an Umbraco:CMS:Global:Smtp section (host, port, sender address and credentials) to appsettings.Production.json (Step 8); Umbraco sends password reset, invitation and notification e-mail through it.
  • Upgrades and packages: Umbraco and its packages are NuGet packages compiled into the site, so upgrades are a rebuild of the site. cloudimg publishes new versions of this image for Umbraco and .NET security releases; to move an existing site, back it up (Step 7), launch the new version and restore /var/lib/umbraco and /etc/umbraco into it (Umbraco upgrades the database on start). Install only packages you trust: a package is .NET code that runs inside Umbraco.
  • Logs: Settings > Log Viewer shows Umbraco's own log; the files are in /var/lib/umbraco/umbraco-runtime/Logs, and the service log is sudo journalctl -u umbraco.

Step 12: Server components

dpkg-query -W -f='${Package} ${Version}\n' aspnetcore-runtime-10.0 nginx sqlite3
aspnetcore-runtime-10.0 10.0.14-0ubuntu1~24.04.1
nginx 1.24.0-2ubuntu7.19
sqlite3 3.45.1-1ubuntu2.8
Component Where
Umbraco 17.8.0 /opt/umbraco/app (read-only), service umbraco (Kestrel on 127.0.0.1:5000)
nginx /etc/nginx/sites-available/cloudimg-umbraco, TLS in /etc/nginx/ssl
Data, keys and edited files /var/lib/umbraco
Instance settings /etc/umbraco/appsettings.Instance.json
First boot umbraco-firstboot.service, /usr/local/sbin/umbraco-firstboot.sh
Helpers /usr/local/sbin/umbraco-set-hostname, /usr/local/sbin/umbraco-cred-roundtrip

Step 13: The rebuilt back office

Umbraco 17.8.0's published back office bundles prosemirror-view 1.42.2, the library under its Tiptap rich-text editor. That version runs pasted HTML through less validation than it should (GHSA-c8x8-7fp4-3x9w, CVE-2026-104847, high severity; fixed in 1.42.3), and no Umbraco release ships the fix yet. This image builds the back office from Umbraco's own release-17.8.0 source with one change: an npm override that raises prosemirror-view to 1.42.3 (MIT licence), so the lock file differs in that one package. The build is reproducible, and only the rich-text editor's files differ from Umbraco's published back office. The details are recorded on the instance:

grep -A2 '^Back office' /usr/share/doc/umbraco/RELEASE
grep -rlF '"ProseMirrorClipboard"' /opt/umbraco/app/wwwroot/umbraco/backoffice --include='*.js' | sed 's#.*/backoffice/#backoffice/#'
Back office (d-742): src/Umbraco.Web.UI.Client of release-17.8.0 (commit 83984e2515634f5812d9af99e0149cd7d94622c8, tarball sha256 1b737566fd107b9c0e66c88fc82136f5468c29454c20596d2165d462684b1ce5) rebuilt with ONE npm override,
  prosemirror-view 1.42.2 -> 1.42.3 (GHSA-c8x8-7fp4-3x9w / CVE-2026-104847, paste XSS; MIT). Node.js v24.22.2 (sha256 48cc0e21e048790e54c0cf5749c2725005313d652651c40b7a21feb9697aaf75), npm ci + npm run build:for:cms.
  Output: 2459 files, digest 0b4e3bc2915b8f63c483714476675bdea67f492d7c1dacb8802c023b81205497 (reproduced on the build workstation three times and on this builder).
backoffice/packages/tiptap/tiptap-toolbar-element-api-base-705jTA9z.js

When an Umbraco release ships prosemirror-view 1.42.3 or later, cloudimg's next version of this image uses Umbraco's own back office again.

Step 14: Security recommendations

  • Allow port 22 in the security group from your own networks only.
  • Change the administrator's e-mail to your own (Step 4), keep its password secret and change it if it may have been shared (Step 11). Give each editor their own user with only the sections they need.
  • Keep the database and the data protection keys secret and backed up (Step 7).
  • Use your own DNS name and a certificate your browsers trust (Step 10).
  • Keep Umbraco on the newest release of the 17 LTS line (Step 11) and install only packages you trust.
  • Keep the operating system patched; unattended security upgrades are on, and sudo apt-get update && sudo apt-get upgrade applies everything else (the .NET runtime included).

Support and licensing

Umbraco CMS is free software under the MIT License (a copy is at /usr/share/doc/umbraco/LICENSE.md); prosemirror-view is under the MIT License; ImageSharp is under the Six Labors Split License, which grants the Apache License 2.0 when it is used as a dependency of open source software such as Umbraco; the other bundled libraries carry their own open source licences. There is no per user or per CPU licence fee and no licence key; Umbraco's commercial add-ons (Forms, Deploy, Workflow) are not installed. This image builds the upstream release with the one library raise described in Step 13, plus the cloudimg first boot, settings and helpers described above. The AMI price covers cloudimg's hardening, first boot setup and support. cloudimg is not affiliated with or endorsed by Umbraco A/S or the .NET Foundation.

  • Email: support@cloudimg.co.uk
  • Website: https://www.cloudimg.co.uk
  • Support: 24/7, with a 24 hour response SLA