Deploy on a VPS
What you'll build
Your site with Easy CMS on one Linux server you control (Ubuntu or Debian): Node.js, SQLite and uploads on the server's disk, systemd to keep it running, Caddy for HTTPS, and daily backups.
Before this page: Choosing a host, Migrations & deployment.
One server is the simplest and cheapest way to run Easy CMS. The server keeps running, so it runs scheduled jobs every minute by itself, and SQLite needs nothing but a file.
1. Node.js and a user
Install Node.js 22.12 or newer (24 LTS), for example from nodejs.org or your distribution's NodeSource packages, then create a user that runs the site and owns its files:
node -v # v22.12 or newer
sudo useradd --system --create-home --shell /usr/sbin/nologin cms
sudo mkdir -p /srv/site && sudo chown cms:cms /srv/site2. The site
As the cms user, put the code in /srv/site (git clone, or copy it), add the secrets, install and build. Build on the server itself: SQLite's native driver must match its system.
sudo -u cms bash
cd /srv/site
git clone https://github.com/you/your-site.git .
printf 'EASY_CMS_SECRET=%s\nEASY_CMS_SETUP_CODE=%s\n' "$(openssl rand -hex 32)" "choose-a-phrase" > .env
chmod 600 .env
npm ci
npm run build # Next.js or Nuxt; nothing to build for the standalone server
exitWithout DATABASE_URL, sqlite({ url: process.env.DATABASE_URL ?? 'file:./cms.db' }) keeps the database in /srv/site/cms.db, and uploads go to /srv/site/uploads. Postgres works too: install it on the server or use a hosted one, and set DATABASE_URL in .env.
3. A systemd service
systemd starts the site at boot, applies migrations before each start and restarts it if it stops.
[Unit]
Description=Easy CMS site
After=network.target
[Service]
Type=simple
User=cms
WorkingDirectory=/srv/site
Environment=NODE_ENV=production
Environment=PORT=3000
EnvironmentFile=/srv/site/.env
ExecStartPre=/usr/bin/npx easy-cms migrate
ExecStart=/usr/bin/npx next start
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target[Unit]
Description=Easy CMS site
After=network.target
[Service]
Type=simple
User=cms
WorkingDirectory=/srv/site
Environment=NODE_ENV=production
Environment=PORT=3000
EnvironmentFile=/srv/site/.env
ExecStartPre=/usr/bin/npx easy-cms migrate
ExecStart=/usr/bin/node .output/server/index.mjs
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target[Unit]
Description=Easy CMS site
After=network.target
[Service]
Type=simple
User=cms
WorkingDirectory=/srv/site
Environment=NODE_ENV=production
Environment=PORT=3000
EnvironmentFile=/srv/site/.env
ExecStartPre=/usr/bin/npx easy-cms migrate
ExecStart=/usr/bin/npx easy-cms serve --trust-proxy
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.targetEnvironmentFile gives the server .env (Nuxt doesn't read it by itself), and WorkingDirectory makes file:./cms.db, the migrations and uploads resolve in the project. Check where npx and node are with which npx and adjust the paths. Then:
sudo systemctl daemon-reload
sudo systemctl enable --now easy-cms
journalctl -u easy-cms -f # the applied migrations, then the serverHTTPS with Caddy
Caddy gets and renews the HTTPS certificate by itself. Point the domain's DNS at the server, open ports 80 and 443, install Caddy and use:
cms.example.com
reverse_proxy localhost:3000sudo systemctl reload caddyCaddy redirects HTTP to HTTPS by itself. It sends X-Forwarded-For, X-Forwarded-Proto and X-Forwarded-Host, so trust it: trustProxy: true in the Next.js route handlers or the Nuxt module (--trust-proxy for the standalone server, already in the unit above). Set serverURL: 'https://cms.example.com' in the config. With nginx, proxy to the same port and set those headers yourself, and redirect HTTP to HTTPS with a server block on port 80:
server {
listen 80;
server_name cms.example.com;
return 301 https://$host$request_uri;
}Backups
Let Easy CMS back up the database every day (see Backups):
backups: { frequency: 'daily', at: '03:00', keep: 7 },Backups go to backups/ on the same disk, so copy them and uploads/ somewhere else too: Automate backups uses a cron job with S3 or rclone.
Updating
cd /srv/site
sudo -u cms git pull
sudo -u cms npm ci
sudo -u cms npm run build
sudo systemctl restart easy-cms # migrates, then startsThe site is down for the few seconds of the restart.