Documentation

Last updated: 2026-09-15

Running Everlock under systemd with socket activation

This is a full setup for a native Linux host. systemd binds each listening socket and passes it to Everlock, which gives you two things: an unprivileged process (systemd binds the low ports, so Everlock doesn't need to), and graceful restarts (systemd keeps the socket bound across a restart — covered at the end).

Everlock adopts each inherited socket by a name that the socket unit declares as FileDescriptorName. A name identifies a role, not a single socket: list several ListenStream= lines under one name — or point several socket units at the same name — and Everlock serves every one of them the same way. That is how one frontend reaches both IPv4 and IPv6, or several ports; see IPv4 and IPv6. The names and their default ports:

FrontendFileDescriptorNamePort
HTTPhttp80
HTTPShttps443
Git/admin SSHssh2222
SMTP (inbound)smtp-inbound25
SMTP (submission)smtp-submission587
IMAPSimap-tls993
IMAP STARTTLSimap-starttls143
DNS (UDP)dns-udp53
DNS (TCP)dns-tcp53

You only create the units for the services you actually run — skip the rest.

1. Download the binary

Fetch the build for your CPU. This is the build for 64-bit x86; browse everlock.sh/dl/ for arm64 and CPU feature levels (amd64v2 / amd64v3 / amd64v4). Every build embeds the same model — see the embedded model.

curl -fL https://everlock.sh/dl/latest/everlock-linux-amd64 -o everlock
chmod +x everlock
./everlock version    # confirms the build, model, and platform

The binary is installed in the next step, into a directory the service user owns: the self-update replaces the binary from inside the service, and the unit's ProtectSystem=strict hardening leaves only the state directory writable.

2. Create a service user, data directory, and install the binary

Everlock runs unprivileged. The binary lives at /var/lib/everlock/bin/everlock, owned by the service user, so the daily self-update can swap it in place under the unit's filesystem sandbox (StateDirectory keeps /var/lib/everlock writable while ProtectSystem=strict makes everything else read-only):

sudo useradd --system --home /var/lib/everlock --shell /usr/sbin/nologin everlock
sudo install -d -o everlock -g everlock /var/lib/everlock /var/lib/everlock/bin
sudo install -m 0755 -o everlock -g everlock everlock /var/lib/everlock/bin/everlock

To keep everlock on the shell PATH, link it — the symlink always resolves to the current binary, including after a self-update:

sudo ln -s /var/lib/everlock/bin/everlock /usr/local/bin/everlock

3. Create the socket units

One .socket file per port. They are nearly identical — only ListenStream, FileDescriptorName, and the description change. Create only the ones you need; each unit's FileDescriptorName must exactly match the name in the table above.

HTTP — /etc/systemd/system/everlock-http.socket:

[Unit]
Description=Everlock HTTP socket
Wants=network-online.target
After=network-online.target

[Socket]
ListenStream=80
FileDescriptorName=http
Service=everlock.service

[Install]
WantedBy=sockets.target

HTTPS — /etc/systemd/system/everlock-https.socket:

[Unit]
Description=Everlock HTTPS socket
Wants=network-online.target
After=network-online.target

[Socket]
ListenStream=443
FileDescriptorName=https
Service=everlock.service

[Install]
WantedBy=sockets.target

Git/admin SSH — /etc/systemd/system/everlock-ssh.socket:

[Unit]
Description=Everlock SSH socket
Wants=network-online.target
After=network-online.target

[Socket]
ListenStream=2222
FileDescriptorName=ssh
Service=everlock.service

[Install]
WantedBy=sockets.target

SMTP inbound — /etc/systemd/system/everlock-smtp-inbound.socket:

[Unit]
Description=Everlock SMTP inbound socket
Wants=network-online.target
After=network-online.target

[Socket]
ListenStream=25
FileDescriptorName=smtp-inbound
Service=everlock.service

[Install]
WantedBy=sockets.target

SMTP submission — /etc/systemd/system/everlock-smtp-submission.socket:

[Unit]
Description=Everlock SMTP submission socket
Wants=network-online.target
After=network-online.target

[Socket]
ListenStream=587
FileDescriptorName=smtp-submission
Service=everlock.service

[Install]
WantedBy=sockets.target

IMAPS — /etc/systemd/system/everlock-imap-tls.socket:

[Unit]
Description=Everlock IMAPS socket
Wants=network-online.target
After=network-online.target

[Socket]
ListenStream=993
FileDescriptorName=imap-tls
Service=everlock.service

[Install]
WantedBy=sockets.target

DNS answers on both UDP and TCP port 53, so it takes two units — note the UDP one uses ListenDatagram. /etc/systemd/system/everlock-dns-udp.socket:

[Unit]
Description=Everlock DNS UDP socket
Wants=network-online.target
After=network-online.target

[Socket]
ListenDatagram=53
FileDescriptorName=dns-udp
Service=everlock.service

[Install]
WantedBy=sockets.target

/etc/systemd/system/everlock-dns-tcp.socket:

[Unit]
Description=Everlock DNS TCP socket
Wants=network-online.target
After=network-online.target

[Socket]
ListenStream=53
FileDescriptorName=dns-tcp
Service=everlock.service

[Install]
WantedBy=sockets.target

The two lines that matter in each: Service=everlock.service routes the socket's descriptor to the shared service, and FileDescriptorName is the name Everlock looks it up by. Get it wrong and that socket is bound by systemd with nothing accepting on it — connections complete the TCP handshake and then hang, rather than being refused. Everlock reports unrecognised names at startup:

ERROR socket activation: inherited socket(s) named ["http6"] match no frontend
and will never be served — connections to them hang. Known names: [...]

Leaving FileDescriptorName= out does not mean "no name": it defaults to the unit name including the .socket suffix, which matches nothing. The Wants=/After=network-online.target pair holds each bind until the network is actually up — socket units start early in boot, before the network is configured.

The units above bind every address on the host. To name addresses instead — required on a machine that also runs an OS sshd or a systemd-resolved stub, since a wildcard would collide with them — list one ListenStream= per address and add two settings:

[Socket]
ListenStream=198.51.100.7:80
ListenStream=[2001:db8::1]:80
BindIPv6Only=ipv6-only
FreeBind=yes
FileDescriptorName=http
Service=everlock.service

BindIPv6Only= otherwise follows the host's net.ipv6.bindv6only sysctl, a value you inherit rather than choose. FreeBind=yes lets the socket bind an address the interface has not configured yet — systemd recommends it for any specific-address bind, and here it is load-bearing, because these units are Requires= of the service and a socket that cannot bind at boot takes Everlock down with it. IPv4 and IPv6 covers the full picture, including doing it without socket activation.

4. Create the service unit

/etc/systemd/system/everlock.service. In Requires= and After=, list only the sockets you created, and in ExecStart enable the matching frontends:

[Unit]
Description=Everlock
Wants=network-online.target
After=network-online.target
# Bind the sockets before we start, and keep them bound across our restarts.
Requires=everlock-http.socket everlock-https.socket everlock-ssh.socket \
         everlock-smtp-inbound.socket everlock-smtp-submission.socket \
         everlock-imap-tls.socket everlock-dns-udp.socket everlock-dns-tcp.socket
After=everlock-http.socket everlock-https.socket everlock-ssh.socket \
      everlock-smtp-inbound.socket everlock-smtp-submission.socket \
      everlock-imap-tls.socket everlock-dns-udp.socket everlock-dns-tcp.socket

[Service]
Type=simple
User=everlock
Group=everlock
StateDirectory=everlock
ExecStart=/var/lib/everlock/bin/everlock serve \
    --data-dir /var/lib/everlock \
    --frontend-http \
      --frontend-http-listen-http 0.0.0.0:80 \
      --frontend-http-listen-https 0.0.0.0:443 \
    --frontend-ssh --frontend-ssh-listen 0.0.0.0:2222 \
    --frontend-smtp --frontend-smtp-listen 0.0.0.0:25 \
    --frontend-smtp-submission --frontend-smtp-submission-listen 0.0.0.0:587 \
    --frontend-imap --frontend-imap-tls-listen 0.0.0.0:993 \
    --frontend-dns \
      --frontend-dns-listen-udp 0.0.0.0:53 \
      --frontend-dns-listen-tcp 0.0.0.0:53

# Supervision + the drain window for a graceful restart.
Restart=always
RestartSec=1
TimeoutStopSec=90

# Optional hardening (relax if a backend needs more filesystem access).
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
PrivateTmp=true

[Install]
WantedBy=multi-user.target

A few things worth knowing:

  • The listen addresses in ExecStart are not used for a socket-activated frontend — they only decide whether it is enabled (HTTPS starts because --frontend-http-listen-https is present). Every address comes from the .socket unit. They are worth keeping readable, but do not edit them expecting a listener to move; edit the socket unit. Note this also means the unprivileged service never binds these ports itself: with User=everlock and no AmbientCapabilities=CAP_NET_BIND_SERVICE, a frontend whose socket Everlock cannot find has no working fallback for any port below 1024.
  • Restart=always is required for the self-update: a socket-activated update drains and exits, and this is what brings the new binary back up.
  • TimeoutStopSec is your drain cap — in-flight requests get up to this long to finish before systemd kills the old process.

5. mDNS (optional, not socket-activated)

Unlike DNS, mDNS relies on multicast, which can't be socket-activated — so it gets no .socket unit and is enabled purely through a flag on ExecStart:

    --frontend-mdns

mDNS advertises on the local network (UDP 5353, a high port needing no special privilege) and needs the host's real network — it won't work behind network namespacing that hides the LAN. Because it isn't socket-activated, a restart briefly rebinds its socket; for discovery traffic that's invisible.

Mixing the two needs nothing special in the unit: the same always-on process adopts the systemd-passed sockets and binds mDNS's own socket itself. The only consequence is that the service keeps host-network access (no PrivateNetwork= isolation) — which Everlock needs anyway for outbound updates and ACME, so it isn't a trade-off mDNS introduces on its own.

Everything else, DNS included, is socket-activated above — so the service needs no capabilities: systemd binds every privileged port (including 53) and hands it over, and the process runs fully unprivileged.

6. Enable and start

sudo systemctl daemon-reload
sudo systemctl enable --now everlock.service

Requires= pulls in and binds the sockets first, then starts the service, which adopts them.

Stopping for maintenance

systemctl stop everlock stops only the service — the socket units stay bound, and the next inbound connection starts the service right back up. On an internet-facing host that happens within seconds (DNS queries, mail delivery attempts, HTTP scanners). For work that needs Everlock to stay down — store surgery, moving the data directory — take the sockets down with it:

systemctl stop 'everlock*'          # the service and every everlock-* socket
systemctl is-active everlock        # confirm: inactive

# … maintenance …

systemctl start everlock-http.socket everlock-https.socket everlock-ssh.socket \
                everlock-smtp-inbound.socket everlock-smtp-submission.socket \
                everlock-imap-tls.socket everlock-dns-udp.socket everlock-dns-tcp.socket
systemctl start everlock.service

Start the sockets explicitly (a systemctl start 'everlock*' glob usually has no effect — globs only expand to loaded units), then the service, which adopts them.

7. Verify

# The startup log should mention adopting inherited listeners:
journalctl -u everlock -b | grep -i socket-activated
#   → "socket-activated by systemd; adopting inherited listeners"

# One line per listener actually being served — count them. With several
# sockets under one name this is the authoritative answer, not the unit files.
journalctl -u everlock -b | grep -i listening

systemctl list-sockets | grep everlock     # the bound ports + their service
ss -tulnp | grep -E ':(80|443|2222|25|587|993|53)\b'   # -u also lists the DNS UDP socket

8. Get the bootstrap admin password and connect

On its first start with no configuration, Everlock creates an admin user with an auto-generated password and logs it once — it is never shown again. Grab it from the journal:

journalctl -u everlock -b | grep -A2 'bootstrap admin credentials'
#   username: admin
#   password: <auto-generated — copy it now>

Then connect to the admin console and enable the backends you want — the sockets are just transports; services like sites, Git, mail, and the vault are turned on here:

ssh -p 2222 admin@your-host   # use the SSH port from your everlock-ssh.socket
# /backends enable site-http git-ssh …

How updates and graceful restart work

This is the payoff of the socket-activation setup. Everlock never rebinds the ports on a restart — systemd keeps them, so the connection backlog survives while the new process takes over:

sequenceDiagram
participant sd as systemd
participant old as Everlock (old)
participant new as Everlock (new)
sd->>old: SIGTERM (self-update / restart)
old->>old: stop accepting, drain in-flight
old-->>sd: exit(0) — socket stays bound by systemd
sd->>new: start, pass sockets via LISTEN_FDS
new->>new: adopt the same fds (no rebind)
Note over sd,new: queued connections preserved
A restart under socket activation: systemd keeps the socket bound, so the backlog survives while the new process takes over.
  • Automatic updates (on by default). Once a day Everlock checks everlock.sh, and on a new digest it downloads, verifies, and applies the binary — then takes the graceful restart above. Because systemd holds the sockets, the swap is seamless. To turn it off, add --no-update to ExecStart (hard-off, for pinning a recovered binary) or flip it at runtime with /server settings set update.enabled false. The self-update page covers the full mechanism, manual updates, and rollback.
  • Manual updates. Replace /var/lib/everlock/bin/everlock (keep it owned by the everlock user) and sudo systemctl restart everlock — the restart drains and re-adopts the same sockets. The apply path also keeps the previous binary as everlock.old for a quick rollback.
deployment systemd socket-activation operations