Deployment guide

Oqtane 10.2.6

Overview

Oqtane is a free and open source content management system and modular application framework for .NET. It runs on ASP.NET Core and Blazor. Sites are built from pages, themes and modules (rich text, files, forms and your own Blazor modules), with users, roles and permissions, multiple sites per installation, search, localization and a full administration area.

This guide covers the SecureAnyCloud Oqtane 10.2.6 on Ubuntu 24.04 AMI on Amazon EC2. It is one version of the SecureAnyCloud BookStack, CMS, Blogs and Wikis listing on AWS Marketplace. You pick it as the software version when you subscribe and launch.

What you get:

  • No reachable installer, no shared database. A fresh Oqtane opens its installation wizard to whoever visits first. This image ships no database, no encryption keyring and no installation settings at all. Your instance's first boot generates the host (superuser) password and runs Oqtane's own unattended installer before the web server starts. The installer runs as the unprivileged oqtane user on a temporary local port, and the installer process is then stopped and checked to be gone.
  • Secrets unique to your instance, never on a command line. The host password, the ASP.NET Core data protection keys that protect sign-in cookies, 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 patched runtime and patched libraries. Oqtane runs on the ASP.NET Core 10.0.14 runtime from Ubuntu's main archive, the October 2026 .NET security release. The Oqtane 10.2.6 release bundles Microsoft.AspNetCore.Components.WebAssembly.Server 10.0.12 (two high-severity advisories, GHSA-g27p-qf6j-c96c and GHSA-qjpx-jqm5-h2c4) and SixLabors.ImageSharp 4.1.1 (four high-severity image-processing advisories). On this image, the official NuGet packages of the fixed releases replace them: the WebAssembly packages 10.0.14 and ImageSharp 4.1.3. They are checked against NuGet's published checksums and sit inside the version ranges Oqtane itself declares. Oqtane's dependency manifest names exactly those versions.
  • Closed by default. Public registration is off, and Oqtane makes no calls home. The module and theme marketplace, the update check and the installation registration, which contact www.oqtane.net, are switched off. Step 8 shows how to switch them back on.
  • HTTPS from the first boot. nginx serves Oqtane on port 443 with a certificate generated on your instance's first boot. Port 80 redirects to HTTPS.
  • Fail closed. nginx and Oqtane 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 every sign-in, two-factor, password reset, login link and registration route. This holds in every spelling Oqtane accepts (any letter case, doubled or trailing slashes, a site path prefix). nginx uses the address of the connection, never a header a client sends. The host password is 25 random characters.
  • Requests from other sites cannot act for you. Every request that changes something (any method but GET) and every WebSocket connection, including Blazor's interactive circuit, must come from your site's own address. The links that start a server-side download from a URL, a framework upgrade, a package install or a scheduled job work only when they come from your site itself (Step 10).
  • The real host and client address. nginx hands Oqtane the host name you configure and nothing a request claims. The site's links, sitemap and password reset e-mails therefore use your host name, and Oqtane's logs record the address of the connection.
  • Hardened defaults. Oqtane's web server (Kestrel) listens on the loopback interface only, behind nginx, and runs as the oqtane user with systemd's sandboxing. Oqtane, 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

Before you launch the instance, you need:

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

Launch the instance

In the AWS Marketplace listing, choose Continue to Subscribe, then Continue to Configuration. Pick Oqtane 10.2.6 on Ubuntu 24.04 as the software version and select your Region. Then choose Launch through EC2.

Choose 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=oqtane}]'
2

Connect to your instance

Version SSH user

Oqtane 10.2.6 on Ubuntu 24.04 ubuntu

ssh -i <your-key.pem> ubuntu@<public-ip>
3

Check the services

First boot takes about a minute, because Oqtane's installer creates the database and the default site. Afterwards, Oqtane and nginx are active, and the first-boot and hairpin units are enabled:

systemctl is-active oqtane nginx
systemctl is-enabled oqtane nginx oqtane-firstboot oqtane-hairpin
active active enabled enabled enabled enabled

Only SSH and nginx listen on the network. Oqtane'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 4096 0.0.0.0:22 0.0.0.0:* sshd LISTEN 0 511 0.0.0.0:80 0.0.0.0:* nginx LISTEN 0 511 0.0.0.0:443 0.0.0.0:* nginx LISTEN 0 512 127.0.0.1:5000 0.0.0.0:* dotnet LISTEN 0 4096 [::]:22 [::]:* sshd LISTEN 0 511 [::]:80 [::]:* nginx LISTEN 0 511 [::]:443 [::]:* nginx

Check the runtime and the release:

dotnet --list-runtimes grep -m1 . /usr/share/doc/oqtane/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] Oqtane 10.2.6 (github.com/oqtane/oqtane.framework, MIT), Oqtane.Framework.10.2.6.Install.zip sha256 6cdb0091ecffb9dcdf10d821c5cf7674fbb7b6676208f56a1df77e8677646473
4

Get your sign-in details

The credentials file holds the address Oqtane is served as (your instance's public DNS name by default), the sign-in address, the host user and its password. All of these are generated on this instance. Print the file:

sudo cat /root/oqtane-credentials.txt
OQTANE_URL=https://ec2-<public-ip-dashed>.<region>.compute.amazonaws.com/ OQTANE_LOGIN_URL=https://ec2-<public-ip-dashed>.<region>.compute.amazonaws.com/login OQTANE_ADMIN_USER=host OQTANE_ADMIN_EMAIL=admin@example.com OQTANE_ADMIN_PASSWORD=<12 letters and digits>-<12 letters and digits>

host is Oqtane's host account. It is the superuser of the installation, with every site's administration and the system pages (System Info, System Update, Sql Management). There is no other login.

Its e-mail starts as admin@example.com. Change it to your own address in your profile (click host at the top right). Configure SMTP in Admin Dashboard > Site Settings before you rely on password reset e-mails.

5

Visit the site and sign in

Open OQTANE_URL in a browser. The certificate was generated on your instance, so the browser warns that it does not know the issuer. Compare the certificate's SHA-256 fingerprint with the one in Step 10 and continue, or install your own certificate (Step 11). The default site opens.

Choose Login (or open OQTANE_LOGIN_URL), and enter host and the password from Step 4.

Once you are signed in, the gear button at the top right opens the control panel, and /admin opens the Admin Dashboard.

System Info (/admin/system) shows the framework version, the .NET runtime and where Oqtane keeps its files.

6

Edit content

Turn on edit mode with the pencil button at the top right, then use a module's menu to Edit it. Text modules open Oqtane's rich text editor, with a formatting toolbar, links, images and an HTML source view.

Save publishes the change at once.

Add pages in Admin Dashboard > Page Management, upload files in File Management, and manage users and roles in User Management and Role Management. Each module, page and folder carries its own view and edit permissions.

7

Your data

Oqtane keeps its database (SQLite), its data protection keys and the site's uploaded files on the instance. List the data and key directories:

sudo ls -la /var/lib/oqtane/data /var/lib/oqtane/.aspnet/DataProtection-Keys | sed -E 's/key-[0-9a-f-]+\.xml/key-<id>.xml/'
/var/lib/oqtane/.aspnet/DataProtection-Keys: total 12 drwx------ 2 oqtane oqtane 4096 Oct 31 15:32 . drwx------ 3 oqtane oqtane 4096 Oct 31 15:31 .. -rw------- 1 oqtane oqtane 1001 Oct 31 15:32 key-<id>.xml /var/lib/oqtane/data: total 500 drwxr-x--- 2 oqtane oqtane 4096 Oct 31 15:50 . drwxr-x--- 4 oqtane oqtane 4096 Oct 31 15:31 .. -rw-r--r-- 1 oqtane oqtane 499712 Oct 31 15:50 Oqtane.db
  • /var/lib/oqtane/data/Oqtane.db: pages, modules, content, users, settings and logs.
  • /var/lib/oqtane/.aspnet/DataProtection-Keys: the keys that protect sign-in cookies and other protected values. Back them up with the database. Without them, every user has to sign in again.
  • /opt/oqtane/app/Content: the uploaded files of each site, created when you upload the first file.
  • /opt/oqtane/app: the Oqtane application. It belongs to the oqtane user, because Oqtane installs modules, themes and framework upgrades into it.

For a consistent backup, stop Oqtane, copy those paths, and start it again (sudo systemctl stop oqtane, then sudo systemctl start oqtane). Alternatively, take an EBS snapshot of the volume.

Oqtane can also use SQL Server, MySQL or PostgreSQL instead of SQLite (see Oqtane's documentation). This image is configured for SQLite.

8

The closed-by-default settings

Public registration is off. To let visitors create accounts, open Admin Dashboard > Site Settings and turn on Allow Registration.

Oqtane's marketplace of modules and themes, its update check and the installation registration contact www.oqtane.net. They are off on this image because PackageRegistryUrl is empty in /opt/oqtane/app/appsettings.Production.json:

sudo grep -n 'PackageRegistryUrl' /opt/oqtane/app/appsettings.Production.json
8: "PackageRegistryUrl": "",

To switch them on, set the value to https://www.oqtane.net and restart Oqtane:

sudo sed -i 's#"PackageRegistryUrl": ""#"PackageRegistryUrl": "https://www.oqtane.net"#' /opt/oqtane/app/appsettings.Production.json
sudo systemctl restart oqtane

You can always install modules and themes from a package file that you upload in Module Management or Theme Management. Install only packages you trust, because a module is .NET code that runs inside Oqtane.

9

Behind a proxy or load balancer

nginx hands Oqtane 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 and forward to port 443 on the instance. Set the instance's host name to the name your users browse to (Step 11). Oqtane 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.

10

Security checks

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

H="$(sudo sed -n 's#^OQTANE_URL=https://\([^/]*\)/$#\1#p' /root/oqtane-credentials.txt)" curl -s -o /dev/null -w '%{http_code} %{redirect_url}\n' "http://${H}/login" 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/login 000

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

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

Oqtane's server-side components call Oqtane's own API at your host name. The instance therefore trusts this one certificate for itself (/usr/local/share/ca-certificates/sac-oqtane-instance.crt) and keeps those calls on the instance. Do not add the certificate to the trust store of your workstation. Compare the fingerprint instead, or use a certificate from a public authority (Step 11).

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

curl -s -m 5 -o /dev/null -w '%{http_code}\n' "http://$(hostname -I | awk '{print $1}'):5000/" || true systemctl show -p IPAddressDeny oqtane nginx oqtane-firstboot
000 IPAddressDeny=169.254.169.254/32 168.63.129.16/32 fd00:ec2::254/128 IPAddressDeny=169.254.169.254/32 168.63.129.16/32 fd00:ec2::254/128 IPAddressDeny=169.254.169.254/32 168.63.129.16/32 fd00:ec2::254/128

Oqtane links and its sitemap use the configured https host, whatever a request claims. Test this with forged forwarding headers:

H="$(sudo sed -n 's#^OQTANE_URL=https://\([^/]*\)/$#\1#p' /root/oqtane-credentials.txt)" curl -s -H 'X-Forwarded-Host: evil.example' -H 'X-Forwarded-Proto: http' "https://${H}/robots.txt"; echo
User-agent: * Sitemap: https://ec2-<public-ip-dashed>.<region>.compute.amazonaws.com/sitemap.xml

nginx refuses a cross-origin request that changes something, and a cross-site link to a guarded route, with 403:

H="$(sudo sed -n 's#^OQTANE_URL=https://\([^/]*\)/$#\1#p' /root/oqtane-credentials.txt)" curl -s -o /dev/null -w '%{http_code}\n' -H 'Origin: https://evil.example' -d '{}' "https://${H}/api/user/logout" curl -s -o /dev/null -w '%{http_code}\n' -H 'Sec-Fetch-Site: cross-site' "https://${H}/api/job/start/1"
403 403

The sign-in limit applies per network address. The instance's loopback traffic is Oqtane's own and is not limited, so run this test from the instance's own network address. The first attempts reach Oqtane and return 400, because these test requests carry no form token. The rest are refused with 429:

H="$(sudo sed -n 's#^OQTANE_URL=https://\([^/]*\)/$#\1#p' /root/oqtane-credentials.txt)" P="$(hostname -I | awk '{print $1}')" for i in $(seq 1 14); do curl -s -o /dev/null -w '%{http_code} ' --interface "${P}" --resolve "${H}:443:${P}" -H "Origin: https://${H}" -d 'username=x&password=y' "https://${H}/pages/login/"; done; echo
400 400 400 400 400 400 400 400 400 400 400 429 429 429
11

Your own DNS name and certificate

Point a DNS name at the instance's public address. An Elastic IP keeps the address stable. Then make that name the one Oqtane is served as. The helper creates a new certificate for the name, pins nginx and the request guards to it, renames the site's alias and restarts Oqtane:

sudo oqtane-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/oqtane.crt and /etc/nginx/ssl/oqtane.key with your full chain and key. Keep /usr/local/share/ca-certificates/sac-oqtane-instance.crt in step with the new certificate, or remove it once the certificate chains to a public root. Then 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/sac-oqtane-headers.conf. It is left out while the certificate is self-signed.

12

Passwords, e-mail, upgrades and modules

  • Password: Change the host password in your profile (click host, then Profile). Keep /root/oqtane-credentials.txt up to date, or delete it.
  • E-mail: Configure SMTP in Admin Dashboard > Site Settings > SMTP Settings. Oqtane sends password reset, verification and notification e-mail through it.
  • Upgrades: Oqtane upgrades itself from System Update once the package registry is switched on (Step 8), or from an upgrade package you upload. Take a backup first (Step 7). SecureAnyCloud also publishes new versions of this image.
  • Logs: Admin Dashboard > Event Log shows Oqtane's own log, including sign-in failures. The service log is available with sudo journalctl -u oqtane.
13

Server components

dpkg-query -W -f='${Package} ${Version}\n' aspnetcore-runtime-10.0 nginx sqlite3 grep -E '^ (Microsoft|SixLabors)' /usr/share/doc/oqtane/RELEASE | cut -d'(' -f1
aspnetcore-runtime-10.0 10.0.14-0ubuntu1~24.04.1 nginx 1.24.0-2ubuntu7.19 sqlite3 3.45.1-1ubuntu2.8 Microsoft.AspNetCore.Components.WebAssembly.Server 10.0.14 Microsoft.AspNetCore.Components.WebAssembly 10.0.14 Microsoft.JSInterop.WebAssembly 10.0.14 Microsoft.AspNetCore.Components.WebAssembly.Authentication 10.0.14 SixLabors.ImageSharp 4.1.3

Component Where

Oqtane 10.2.6 /opt/oqtane/app, service oqtane (Kestrel on 127.0.0.1:5000)

nginx /etc/nginx/sites-available/sac-oqtane, TLS in /etc/nginx/ssl

Database and keys /var/lib/oqtane

First boot oqtane-firstboot.service, /usr/local/sbin/oqtane-firstboot.sh

Helpers /usr/local/sbin/oqtane-set-hostname, /usr/local/sbin/oqtane-cred-roundtrip

14

Security recommendations

  • Allow port 22 in the security group from your own networks only.
  • Change the host e-mail to your own (Step 4). Keep its password secret, and change it if it may have been shared (Step 12). Create separate administrator accounts for the people who manage a site, and keep the host account for system tasks.
  • Open registration only when you are ready to moderate it (Step 8).
  • 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 11).
  • Keep Oqtane on the newest release (Step 12), and install only modules and themes you trust.
  • Keep the operating system patched. Unattended security upgrades are on, and sudo apt-get update && sudo apt-get upgrade applies everything else, including the .NET runtime.

Support and licensing

Oqtane is free software under the MIT License (a copy is at /usr/share/doc/oqtane/LICENSE). ImageSharp is under the Six Labors Split License, which grants the Apache License 2.0 for use in open source software such as Oqtane (/usr/share/doc/oqtane/SIXLABORS-IMAGESHARP-LICENSE). The other bundled libraries carry their own open source licenses. There is no per-user or per-CPU license fee and no license key.

This image installs the upstream release with the bundled WebAssembly and ImageSharp libraries raised to their fixed releases, plus the SecureAnyCloud first-boot setup, settings and helpers described above. The AMI price covers SecureAnyCloud's hardening, first-boot setup and support.

SecureAnyCloud is not affiliated with or endorsed by the Oqtane project or the .NET Foundation.

  • Email: sales@secureanycloud.com
  • Website: https://secureanycloud.com
  • Support: 24/7, with a 24 hour response SLA

Deploy on AWS

Launch Oqtane 10.2.6 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

Deploy on AWS