Skip to content

Installing with systemd (Ubuntu / Debian)

This page walks through building engawa from scratch on Ubuntu 24.04 LTS. Debian 12 uses the same package names, so the steps apply there almost unchanged.

If you want to use Docker, follow Installing with Docker instead. For RHEL-family systems (AlmaLinux, Rocky Linux, RHEL), see Installing with systemd (RHEL family).

Item Guideline
Server 4GB of RAM or more recommended. The build step is memory-hungry
Domain e.g. example.com. Point an A record (or AAAA for IPv6) at the server first
Mail server Not required, but you need one to send registration confirmation mails
Object storage Not required. You can configure it later from the admin panel
  1. Install the required software (Node.js, PostgreSQL, Redis)
  2. Create a dedicated user and a database
  3. Fetch the source and build it
  4. Write the configuration file
  5. Register it as a systemd service
  6. Put a reverse proxy (nginx) in front and serve HTTPS

Run the commands below as a regular user with sudo access.

Bring the system up to date and install the build tools.

ターミナルウィンドウ
sudo apt update
sudo apt full-upgrade -y
sudo apt install -y build-essential python3 curl git ca-certificates gnupg ffmpeg

build-essential and python3 are needed to compile some of engawa’s dependencies. Without them, pnpm install fails later on.

ffmpeg is needed to handle video files. engawa shells out to the ffmpeg and ffprobe commands directly, so they have to be present on the system. Confirm they are:

ターミナルウィンドウ
ffprobe -version

engawa needs Node.js 24 or newer. The version in Ubuntu’s own repository is too old, so add the NodeSource repository.

ターミナルウィンドウ
curl -fsSL https://deb.nodesource.com/setup_24.x | sudo -E bash -
sudo apt install -y nodejs

Check it:

ターミナルウィンドウ
node -v

A version starting with v24. means you are good.

Next, enable pnpm. engawa uses pnpm, not npm or yarn.

ターミナルウィンドウ
sudo corepack enable
ターミナルウィンドウ
pnpm -v
ターミナルウィンドウ
sudo apt install -y postgresql postgresql-contrib
sudo systemctl enable --now postgresql

PostgreSQL 15 or newer is required; Ubuntu 24.04 gives you 16. Check with:

ターミナルウィンドウ
psql --version
ターミナルウィンドウ
sudo apt install -y redis-server
sudo systemctl enable --now redis-server

Verify it is running:

ターミナルウィンドウ
redis-cli ping

It should answer PONG.

Running engawa as root is best avoided, so create a dedicated user.

ターミナルウィンドウ
sudo useradd -m -s /bin/bash engawa

From here on, anything touching engawa itself is done as this user. Switch to it with:

ターミナルウィンドウ
sudo -iu engawa

Type exit to return to your own user. Each step below says which user to run it as.

Go back to your own user (the one with sudo) and run:

ターミナルウィンドウ
sudo -u postgres psql

At the postgres=# prompt, run these three lines. Replace example-pass with a password of your own and keep it — you will put it in the configuration file.

CREATE ROLE engawa WITH LOGIN PASSWORD 'example-pass';
CREATE DATABASE engawa OWNER engawa;
\q

\q leaves the prompt.

From here on, work as the engawa user.

ターミナルウィンドウ
sudo -iu engawa

Clone the repository:

ターミナルウィンドウ
git clone --branch develop https://github.com/bettaku/engawa.git engawa
cd engawa

Install the dependencies. This takes a few minutes.

ターミナルウィンドウ
pnpm install --frozen-lockfile

Copy the sample and edit it, still as the engawa user.

ターミナルウィンドウ
cp .config/example.yml .config/default.yml
nano .config/default.yml

At a minimum, adjust these:

# The public URL. The trailing slash is required
url: https://example.com/
# The port engawa listens on. nginx forwards to it
port: 3000
db:
host: localhost
port: 5432
db: engawa
user: engawa
pass: example-pass # the password from step 6
redis:
host: localhost
port: 6379
# Trust the reverse proxy you set up in step 11.
# Without this, every request is recorded as coming from the proxy's IP
trustProxy:
- '127.0.0.1/32'
- '::1/128'
# Do not change this after the server has been created
id: 'aidx'

In nano, save with Ctrl+O → Enter and quit with Ctrl+X.

Every other option is documented with comments in the sample file, so it is worth reading through once. For how the file relates to environment variables, see Backend environment variables; for fine-tuning the database connection, see Options for db.extra.

Still as the engawa user, from the repository root:

ターミナルウィンドウ
NODE_ENV=production pnpm build

Once the build finishes, create the tables:

ターミナルウィンドウ
pnpm migrate

Then start it by hand once to check everything works:

ターミナルウィンドウ
pnpm start

A line like Now listening on port 3000 means it worked. Stop it with Ctrl+C and move on.

If it errors out, re-check the database password and the Redis settings.

Type exit to leave the engawa user and return to your own.

Create the service file:

ターミナルウィンドウ
sudo nano /etc/systemd/system/engawa.service

Paste this in and save:

[Unit]
Description=engawa daemon
After=network-online.target postgresql.service redis-server.service
[Service]
Type=simple
User=engawa
Group=engawa
UMask=0027
WorkingDirectory=/home/engawa/engawa
ExecStart=/usr/bin/node /home/engawa/engawa/packages/backend/built/boot/entry.js
Environment="NODE_ENV=production"
TimeoutSec=60
StandardOutput=journal
StandardError=journal
SyslogIdentifier=engawa
Restart=always
[Install]
WantedBy=multi-user.target

Load and start it:

ターミナルウィンドウ
sudo systemctl daemon-reload
sudo systemctl enable --now engawa

Check the status:

ターミナルウィンドウ
sudo systemctl status engawa

active (running) is what you want. To follow the logs:

ターミナルウィンドウ
sudo journalctl -u engawa -f

Leave with Ctrl+C.

11. Put a reverse proxy in front and serve HTTPS

Section titled “11. Put a reverse proxy in front and serve HTTPS”

engawa is running on localhost:3000, which nothing outside the server can reach. Put nginx in front of it to handle HTTPS and certificates.

The repository ships production-ready proxy configuration in contrib/, and that is what this guide uses. It is worth reading contrib/README.md once as well.

The ACME module needs nginx 1.29.0 or newer, which Ubuntu’s own repository does not carry. Add the official nginx repository.

ターミナルウィンドウ
sudo apt install -y curl gnupg2 ca-certificates lsb-release ubuntu-keyring
ターミナルウィンドウ
curl -fsSL https://nginx.org/keys/nginx_signing.key | gpg --dearmor | sudo tee /usr/share/keyrings/nginx-archive-keyring.gpg > /dev/null
ターミナルウィンドウ
echo "deb [signed-by=/usr/share/keyrings/nginx-archive-keyring.gpg] http://nginx.org/packages/mainline/ubuntu $(lsb_release -cs) nginx" | sudo tee /etc/apt/sources.list.d/nginx.list
ターミナルウィンドウ
sudo apt update
sudo apt install -y nginx nginx-module-acme

Check the version:

ターミナルウィンドウ
nginx -v

nginx/1.29.0 or newer is what you need.

Open the main configuration file:

ターミナルウィンドウ
sudo nano /etc/nginx/nginx.conf

Near the top, outside the http { block, add this one line:

load_module modules/ngx_http_acme_module.so;

Copy contrib/nginx.sample.conf from the repository into nginx’s conf.d:

ターミナルウィンドウ
sudo cp /home/engawa/engawa/contrib/nginx.sample.conf /etc/nginx/conf.d/engawa.conf

Replace the domain and the contact address with your own:

ターミナルウィンドウ
sudo sed -i 's/engawa\.example\.com/example.com/g; s/admin@example\.com/you@example.com/g' /etc/nginx/conf.d/engawa.conf

Open the file and confirm the substitution landed:

ターミナルウィンドウ
sudo nano /etc/nginx/conf.d/engawa.conf

While you are there, check that these match your environment:

Setting Default What it is
resolver 1.1.1.1 1.0.0.1 The DNS servers the ACME module uses. Point it at your own resolver if you have one
ssl_trusted_certificate /etc/ssl/certs/ca-certificates.crt The CA bundle path on Ubuntu and Debian. Leave it as is
client_max_body_size 256m The upload limit. Keep it in line with engawa’s maxFileSize

The official nginx package ships a default site that also listens on port 80. Remove it so it does not compete:

ターミナルウィンドウ
sudo rm -f /etc/nginx/conf.d/default.conf

The certificate and the account key are stored here, and the nginx worker user has to be able to read and write it. With the official package that user is nginx.

ターミナルウィンドウ
sudo install -d -m 700 -o nginx -g nginx /var/lib/nginx/acme-engawa
ターミナルウィンドウ
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp

Port 80 is not just for the redirect to HTTPS — the HTTP-01 challenge uses it for the initial issuance and every renewal. Leave it open.

Check the syntax, then start nginx:

ターミナルウィンドウ
sudo nginx -t
sudo systemctl enable --now nginx

Only start once you see syntax is ok and test is successful. If nginx is already running, use sudo systemctl reload nginx.

The first issuance takes a few tens of seconds, and HTTPS does not work until it completes. Watch the progress and any errors in the nginx log:

ターミナルウィンドウ
sudo tail -f /var/log/nginx/error.log
ターミナルウィンドウ
curl -I http://example.com/

HTTP/1.1 308 Permanent Redirect means the redirect from HTTP works.

ターミナルウィンドウ
curl -I https://example.com/

HTTP/2 200 means the certificate was issued and requests reach engawa.

Open https://example.com in a browser. If the initial setup screen appears, you are done — the first account you create becomes the administrator.

Alternative to step 11: serving HTTPS with Caddy

Section titled “Alternative to step 11: serving HTTPS with Caddy”

This is the Caddy route instead of nginx. If you completed step 11, skip this section. Both want ports 80 and 443, so you cannot run them side by side.

Caddy obtains and renews certificates and redirects HTTP to HTTPS out of the box, so there is no extra module to install.

Ubuntu’s own repository carries Caddy, but it can be outdated, so add the official repository.

ターミナルウィンドウ
sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https curl
ターミナルウィンドウ
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
ターミナルウィンドウ
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
ターミナルウィンドウ
sudo apt update
sudo apt install -y caddy

Check the version:

ターミナルウィンドウ
caddy version

Something starting with v2. means you are good.

ターミナルウィンドウ
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw allow 443/udp

Port 80 is used for issuance and renewal (the HTTP-01 challenge), so leave it open. UDP 443 is for HTTP/3; skip it if you do not want HTTP/3.

Copy contrib/Caddyfile from the repository:

ターミナルウィンドウ
sudo cp /home/engawa/engawa/contrib/Caddyfile /etc/caddy/Caddyfile

Replace the domain and the contact address with your own. The contact address is where Let’s Encrypt sends certificate notices.

ターミナルウィンドウ
sudo sed -i 's/engawa\.example\.com/example.com/g; s/admin@example\.com/you@example.com/g' /etc/caddy/Caddyfile

Open it and confirm the substitutions landed:

ターミナルウィンドウ
sudo nano /etc/caddy/Caddyfile

max_size under request_body (268435456, i.e. 256 MiB) is meant to line up with engawa’s maxFileSize. If you change one, change the other.

Caddy can format and validate the file. Running both before you start it saves time.

ターミナルウィンドウ
caddy fmt --diff /etc/caddy/Caddyfile

A diff here means the formatting is off — it does not mean the config is broken.

ターミナルウィンドウ
sudo caddy validate --config /etc/caddy/Caddyfile --adapter caddyfile

Valid configuration is what you want.

ターミナルウィンドウ
sudo systemctl enable --now caddy

If Caddy is already running, use sudo systemctl reload caddy.

The first issuance takes a few tens of seconds. Watch the progress and any errors in the log:

ターミナルウィンドウ
sudo journalctl -u caddy -f
ターミナルウィンドウ
curl -I http://example.com/

HTTP/1.1 308 Permanent Redirect means the redirect from HTTP works — Caddy sets that up for you.

ターミナルウィンドウ
curl -I https://example.com/

HTTP/2 200 means the certificate was issued and requests reach engawa.

Open https://example.com in a browser. If the initial setup screen appears, you are done — the first account you create becomes the administrator.

When a new version is released, run this as the engawa user:

ターミナルウィンドウ
sudo systemctl stop engawa
sudo -iu engawa
cd engawa
git pull
pnpm install --frozen-lockfile
NODE_ENV=production pnpm build
pnpm migrate
exit
sudo systemctl start engawa

If the release notes describe extra steps, follow those instead.

Symptom What to check
The service keeps dying Read the error with sudo journalctl -u engawa -n 50. A typo in the config file is the usual cause
502 Bad Gateway Check that engawa is running (systemctl status engawa) and that port matches nginx’s proxy_pass
HTTPS does not work / no certificate is issued Check that port 80 is reachable from outside and that DNS points at this server. Details land in /var/log/nginx/error.log
nginx will not start: module ... is already loaded The load_module line is duplicated between nginx.conf and modules-enabled/. Keep only one
Every request appears to come from the same IP trustProxy is missing from the configuration file
The timeline does not update by itself Check that the nginx WebSocket headers (Upgrade and Connection) are present
Cannot connect to the database Try connecting by hand with psql -h localhost -U engawa -d engawa and confirm the password matches the config file
The build stops half way Out of memory. Add swap as described in step 9
Something is off and you are running Caddy Read the log with sudo journalctl -u caddy -n 50
No video thumbnails Check that ffmpeg is installed with ffprobe -version. Restart engawa after installing it

Whenever you edit the configuration file, restart with sudo systemctl restart engawa — a running process will not pick up the change.