EmDash is a new CMS made by Cloudflare, which describes itself as the „spiritual successor to WordPress (that solves plugin security)“ and as „an open source Astro CMS built for humans and agents“.

It can be deployed via Cloudflare, but selfhosting is also possible.

Here is a step-by-step guide to deploy EmDash CMS on a fresh Hetzner VPS using Node.js, SQLite, Caddy webserver, PM2 (node process manager), and sslip.io for automatic SSL certificates without needing a custom domain name (for now).

Beware: This is just for demo purposes, security needs to be checked. Use a hetzner firewall and only open port 22 and 443 for example.

For this tutorial, I quickly purchased a Hetzner CX23 VPS.

Connect to the VPS

ssh root@<IP-of-hetzner-VPS> 

Install Node.js & pnpm

# Download and install nvm (Node Version Manager): curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.8/install.sh | bash # Load nvm into the current shell session in lieu of restarting the terminal \. "$HOME/.nvm/nvm.sh" # Download and install Node.js version 24: nvm install 24 # Verify the Node.js version prints "v24.22.0": node -v # Enable and install pnpm via Node's corepack: corepack enable pnpm # Verify the installed pnpm version: pnpm -v 

Install Caddy webserver

 # 1. Update package lists and install prerequisite packages for Caddy (curl and gnupg) apt update && apt install -y curl gnupg # 2. Download and add the official Caddy GPG signing key to secure apt repositories curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg # Add the Caddy official repository source list configuration curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | tee /etc/apt/sources.list.d/caddy-stable.list # 3. Update APT package lists again to include the new Caddy repository and install Caddy web server apt update apt install -y caddy 

Check that the Caddy background service is active and running correctly: systemctl status caddy

Git pull the demo project

For this demo, I simply installed the marketing starter via pnpm create emdash@latest and pushed it to a public GitHub repository on my account.

# Create a central web root directory for hosting websites mkdir /var/www # Change current directory into the web root cd /var/www # Clone the EmDash marketing starter demo project repo git clone https://github.com/mandrasch/emdash-demo-marketing-starter.git # Move inside the cloned project directory cd emdash-demo-marketing-starter # Install project dependencies using pnpm and compile the production application bundle to serve the site later pnpm i && pnpm run build 

Setup the demo project

# Generate a secure random cryptographic key for encrypting plugin secrets in EmDash EMDASH_KEY=$(pnpm dlx emdash secrets generate) # Fetch the server's public IPv4 address from an external API and store it in a variable SERVER_IP=$(curl -s https://api.ipify.org) # Create and write the runtime environment configuration file (.env) with server settings cat << EOF > /var/www/emdash-demo-marketing-starter/.env PORT=4321 HOST=0.0.0.0 EMDASH_SITE_URL=https://${SERVER_IP}.sslip.io DATABASE_PATH=/var/www/emdash-demo-marketing-starter/data/emdash.db EMDASH_ENCRYPTION_KEY=${EMDASH_KEY} EOF # Create local directories required for storing SQLite data files and uploaded media assets mkdir -p /var/www/emdash-demo-marketing-starter/data/uploads 

Install pm2

PM2 is a daemon process manager for Node.js applications.

# Configure pnpm global bin paths into the shell configuration and reload it pnpm setup && source ~/.bashrc # Install PM2 globally as a Node.js process manager to keep apps running in the background pnpm add -g pm2 

Run our site (via pm2)

# Change directory back to the project folder before starting the process cd /var/www/emdash-demo-marketing-starter # Start the EmDash production server instance via PM2 using the Node.js binary and environment file pm2 start /root/.nvm/versions/node/v24.22.0/bin/node --name "emdash" -- --env-file=.env ./dist/server/entry.mjs 

The command pm2 list now shows the running emdash site:

output of pm2 list command

See the pm2 cheatsheet for all commands.

Configure pm2 to launch on server restarts

# Generate and configure a systemd startup script so PM2 automatically restarts apps on server reboots pm2 startup systemd -u root --hp /root # Save the currently running PM2 process list so it persists across reboots pm2 save 

Create the Caddy configuration

# Fetch the public IP address again to ensure it's available for the Caddyfile configuration block SERVER_IP=$(curl -s https://api.ipify.org) # Create a Caddyfile configuration to reverse-proxy traffic from your sslip.io domain to port 4321 cat << EOF | sudo tee /etc/caddy/Caddyfile ${SERVER_IP}.sslip.io { reverse_proxy localhost:4321 } EOF # Reload Caddy to apply the new reverse proxy configuration and automatically generate an SSL certificate sudo systemctl reload caddy 

Access your site and finish initial installation

Open your browser to test the public site, the images won't be there yet: https://<YOUR-SERVER-IP>.sslip.io

Create the admin account:

https://<YOUR-SERVER-IP>.sslip.io/_emdash/admin

Install step 1, site title

Install step 2, user and email

In step 3, you can generate a passkey for your admin account.

(There is no current option to use a regular password out of the box, see my recent bluesky post for more information about it.)

After the finished setup, the images will be also visible on https://<YOUR-SERVER-IP>.sslip.io:

screenshot of demo website

And you can edit your content in the backend:

Screenshot of pages section

Screenshot of homepage editing

I could not find the setting to lock down the Admin > Content Types section for now, but I'll re-check out the docs.

Where to go from here

This was fairly simple, but to really evaluate it professionally we would need to check the following things:

What would you test first? How is your first impression of EmDash?

More guides