Installing with systemd (RHEL family)
This page walks through building engawa from scratch on AlmaLinux 10, Rocky Linux 10 or RHEL 10. Version 9 and earlier are not supported.
If you are on Ubuntu or Debian, see Installing with systemd (Ubuntu / Debian).
Before you start
Section titled “Before you start”What you need
Section titled “What you need”| 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 |
The plan
Section titled “The plan”- Install the required software (Node.js, PostgreSQL, Valkey)
- Create a dedicated user and a database
- Fetch the source and build it
- Write the configuration file
- Register it as a systemd service
- Put a reverse proxy (nginx) in front and serve HTTPS, including SELinux and firewalld
Run the commands below as a regular user with sudo access.
1. Install the basics
Section titled “1. Install the basics”Bring the system up to date and install the build tools.
sudo dnf upgrade -ysudo dnf group install -y "Development Tools"sudo dnf install -y python3 curl gitDevelopment Tools and python3 are needed to compile some of engawa’s dependencies. Without them, pnpm install fails later on.
Add the EPEL repository now as well, since it is needed later. EPEL packages can depend on the CRB repository, so enable CRB first.
On AlmaLinux and Rocky Linux:
sudo dnf install -y dnf-plugins-coresudo dnf config-manager --set-enabled crbsudo dnf install -y epel-releasednf config-manager comes from dnf-plugins-core, which minimal installs may lack, so install it first.
On RHEL itself (epel-release is not in its standard repositories, so install the RPM that EPEL publishes):
sudo subscription-manager repos --enable codeready-builder-for-rhel-10-$(arch)-rpmssudo dnf install -y https://dl.fedoraproject.org/pub/epel/epel-release-latest-10.noarch.rpmInstall ffmpeg
Section titled “Install ffmpeg”engawa shells out to the ffmpeg and ffprobe commands to handle video. Neither ships in the standard RHEL-family repositories, so you have to add them.
The easy route is ffmpeg-free from EPEL:
sudo dnf install -y ffmpeg-freeIf you need the full build with patent-encumbered codecs, add RPM Fusion:
sudo dnf install -y https://mirrors.rpmfusion.org/free/el/rpmfusion-free-release-10.noarch.rpmsudo dnf install -y ffmpegEither way the commands are called ffmpeg and ffprobe. Confirm they are there:
ffprobe -version2. Install Node.js
Section titled “2. Install Node.js”engawa needs Node.js 24 or newer. The default repositories ship Node.js 24 as nodejs24, but it installs the command as node-24 rather than node, which does not fit the rest of this guide. Add the NodeSource repository instead.
curl -fsSL https://rpm.nodesource.com/setup_24.x | sudo bash -sudo dnf install -y nodejsCheck it:
node -vA version starting with v24. means you are good.
Next, enable pnpm. engawa uses pnpm, not npm or yarn.
sudo corepack enablepnpm -v3. Install PostgreSQL
Section titled “3. Install PostgreSQL”engawa needs PostgreSQL 15 or newer. To get a recent version (18 here), this guide installs it from PostgreSQL’s official Yum repository.
Add the repository
Section titled “Add the repository”sudo dnf install -y https://download.postgresql.org/pub/repos/yum/reporpms/EL-10-$(uname -m)/pgdg-redhat-repo-latest.noarch.rpmInstall and initialize
Section titled “Install and initialize”sudo dnf install -y postgresql18-server postgresql18-contribUnlike Ubuntu, the RHEL family does not initialize the data directory for you. The official packages use a per-version setup command:
sudo /usr/pgsql-18/bin/postgresql-18-setup initdbsudo systemctl enable postgresql-18sudo systemctl start postgresql-18psql and the other commands are also linked into /usr/bin. This page writes full paths under /usr/pgsql-18/bin/ anyway, so the matching version is used even if several are installed.
Enable password authentication
Section titled “Enable password authentication”Out of the box, local connections use ident — no password — which means engawa cannot log in. Change it:
sudo nano /var/lib/pgsql/18/data/pg_hba.confNear the bottom, set the last column of the 127.0.0.1/32 and ::1/128 lines to scram-sha-256:
# IPv4 local connections:host all all 127.0.0.1/32 scram-sha-256# IPv6 local connections:host all all ::1/128 scram-sha-256Save, then reload the configuration:
sudo systemctl reload postgresql-184. Install Valkey (Redis-compatible)
Section titled “4. Install Valkey (Redis-compatible)”The standard RHEL 10 repositories do not carry Redis; they ship Valkey, a compatible fork, instead. engawa connects to it just as it would to Redis.
sudo dnf install -y valkeysudo systemctl enable --now valkeyVerify it is running:
valkey-cli pingIt should answer PONG.
5. Create a user for engawa
Section titled “5. Create a user for engawa”Running engawa as root is best avoided, so create a dedicated user.
sudo useradd -m -s /bin/bash engawaFrom here on, anything touching engawa itself is done as this user. Switch to it with:
sudo -iu engawaType exit to return to your own user. Each step below says which user to run it as.
6. Create the database
Section titled “6. Create the database”Go back to your own user (the one with sudo) and run:
sudo -u postgres /usr/pgsql-18/bin/psqlAt 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. It is worth confirming that the new credentials actually work:
/usr/pgsql-18/bin/psql -h 127.0.0.1 -U engawa -d engawa -c '\conninfo'If it prompts for the password and connects, the authentication change in step 3 took effect.
7. Fetch the source and build
Section titled “7. Fetch the source and build”From here on, work as the engawa user.
sudo -iu engawaClone the repository:
git clone --branch develop https://github.com/bettaku/engawa.git engawacd engawaInstall the dependencies. This takes a few minutes.
pnpm install --frozen-lockfile8. Write the configuration file
Section titled “8. Write the configuration file”Copy the sample and edit it, still as the engawa user.
cp .config/example.yml .config/default.ymlnano .config/default.ymlAt a minimum, adjust these:
# The public URL. The trailing slash is requiredurl: https://example.com/
# The port engawa listens on. nginx forwards to itport: 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 IPtrustProxy: - '127.0.0.1/32' - '::1/128'
# Do not change this after the server has been createdid: '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.
9. Build and initialize the database
Section titled “9. Build and initialize the database”Still as the engawa user, from the repository root:
NODE_ENV=production pnpm buildOnce the build finishes, create the tables:
pnpm migrateThen start it by hand once to check everything works:
pnpm startA 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 Valkey settings.
10. Register it as a systemd service
Section titled “10. Register it as a systemd service”Type exit to leave the engawa user and return to your own.
Create the service file:
sudo nano /etc/systemd/system/engawa.servicePaste this in and save:
[Unit]Description=engawa daemonAfter=network-online.target postgresql-18.service valkey.service
[Service]Type=simpleUser=engawaGroup=engawaUMask=0027WorkingDirectory=/home/engawa/engawaExecStart=/usr/bin/node /home/engawa/engawa/packages/backend/built/boot/entry.jsEnvironment="NODE_ENV=production"TimeoutSec=60StandardOutput=journalStandardError=journalSyslogIdentifier=engawaRestart=always
[Install]WantedBy=multi-user.targetLoad and start it:
sudo systemctl daemon-reloadsudo systemctl enable --now engawaCheck the status:
sudo systemctl status engawaactive (running) is what you want. To follow the logs:
sudo journalctl -u engawa -fLeave 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.
Install nginx and the ACME module
Section titled “Install nginx and the ACME module”The ACME module needs nginx 1.29.0 or newer, which AppStream does not carry. Add the official nginx repository.
sudo tee /etc/yum.repos.d/nginx.repo > /dev/null <<'EOF'[nginx-mainline]name=nginx mainline repobaseurl=http://nginx.org/packages/mainline/centos/$releasever/$basearch/gpgcheck=1enabled=1gpgkey=https://nginx.org/keys/nginx_signing.keymodule_hotfixes=trueEOFIf AppStream’s nginx is already installed, remove it first. If it is not, dnf just says there is no match, and you can move on.
sudo dnf remove -y nginx-coreNow install from the official repository:
sudo dnf install -y nginx nginx-module-acmeCheck the version:
nginx -vnginx/1.29.0 or newer is what you need.
Load the ACME module
Section titled “Load the ACME module”Open the main configuration file:
sudo nano /etc/nginx/nginx.confNear the top, outside the http { block, add this one line:
load_module modules/ngx_http_acme_module.so;Add the site configuration
Section titled “Add the site configuration”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.confReplace 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.confOpen the file, confirm the substitutions landed, and check these against your environment:
sudo nano /etc/nginx/conf.d/engawa.conf| 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 |
Change it to /etc/pki/tls/certs/ca-bundle.crt, as above |
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.confCreate the ACME state directory
Section titled “Create the ACME state directory”The certificate and the account key are stored here, and the nginx worker user has to be able to read and write it.
sudo install -d -m 700 -o nginx -g nginx /var/lib/nginx/acme-engawasudo restorecon -R /var/lib/nginx/acme-engawarestorecon resets the SELinux labels on the directory to the correct values.
Configure SELinux
Section titled “Configure SELinux”Out of the box, SELinux forbids nginx from connecting to another process — engawa, in this case. Allow it:
sudo setsebool -P httpd_can_network_connect 1Configure firewalld
Section titled “Configure firewalld”sudo firewall-cmd --permanent --add-service=httpsudo firewall-cmd --permanent --add-service=httpssudo firewall-cmd --reloadPort 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.
Start it
Section titled “Start it”Check the syntax, then start nginx:
sudo nginx -tsudo systemctl enable --now nginxOnly 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.logVerify
Section titled “Verify”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.
Install Caddy
Section titled “Install Caddy”The standard RHEL-family repositories do not carry Caddy, so install it from COPR.
sudo dnf install -y 'dnf-command(copr)'sudo dnf copr enable -y @caddy/caddysudo dnf install -y caddyCheck the version:
caddy versionSomething starting with v2. means you are good.
Configure firewalld
Section titled “Configure firewalld”sudo firewall-cmd --permanent --add-service=httpsudo firewall-cmd --permanent --add-service=httpssudo firewall-cmd --permanent --add-port=443/udpsudo firewall-cmd --reloadPort 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.
About SELinux
Section titled “About SELinux”Unlike the nginx route, Caddy does not normally need the httpd_can_network_connect boolean: its binary is not labelled as a web server the way nginx’s is, so it runs without those restrictions.
If you do hit a 502, check whether SELinux is involved:
sudo ausearch -m AVC -ts recentAdd the configuration file
Section titled “Add the configuration file”Copy contrib/Caddyfile from the repository:
sudo cp /home/engawa/engawa/contrib/Caddyfile /etc/caddy/CaddyfileReplace 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/CaddyfileOpen it and confirm the substitutions landed:
sudo nano /etc/caddy/Caddyfilemax_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.
Validate the configuration
Section titled “Validate the configuration”Caddy can format and validate the file. Running both before you start it saves time.
caddy fmt --diff /etc/caddy/CaddyfileA diff here means the formatting is off — it does not mean the config is broken.
sudo caddy validate --config /etc/caddy/Caddyfile --adapter caddyfileValid configuration is what you want.
Start it
Section titled “Start it”sudo systemctl enable --now caddyIf 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 -fVerify
Section titled “Verify”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.
Updating
Section titled “Updating”When a new version is released, run this as the engawa user:
sudo systemctl stop engawasudo -iu engawacd engawagit pullpnpm install --frozen-lockfileNODE_ENV=production pnpm buildpnpm migrateexitsudo systemctl start engawaIf the release notes describe extra steps, follow those instead.
When something goes wrong
Section titled “When something goes wrong”| Symptom | What to check |
|---|---|
| 502 Bad Gateway | First confirm you ran sudo setsebool -P httpd_can_network_connect 1. It is the most common cause here |
| Nothing reaches the server at all | Check that http/https are open in firewalld with sudo firewall-cmd --list-services |
| HTTPS does not work / no certificate is issued | Check that ssl_trusted_certificate was changed to /etc/pki/tls/certs/ca-bundle.crt and that port 80 is reachable from outside. 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 service keeps dying | Read the error with sudo journalctl -u engawa -n 50. A typo in the config file is the usual cause |
| Cannot connect to the database | Check that /var/lib/pgsql/18/data/pg_hba.conf says scram-sha-256 and that you ran sudo systemctl reload postgresql-18 |
| The timeline does not update by itself | Check that the nginx WebSocket headers (Upgrade and Connection) are present |
| 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 |
To tell whether SELinux is the culprit, look at the recent denials:
sudo ausearch -m AVC -ts recentWhenever you edit the configuration file, restart with sudo systemctl restart engawa — a running process will not pick up the change.