Deployment guide

Ergo 2.19.1

Overview

Ergo is a modern IRC server written in Go. A classic IRC daemon needs a separate "services" package, but Ergo builds in user accounts (NickServ), channel registration (ChanServ), message history, multiple devices per account, and IRCv3 and SASL login, all in one binary with one configuration file. Ergo has no web interface: you and your users connect with any IRC client.

This guide covers launching and using the Ergo 2.19.1 on Ubuntu 24.04 AMI from SecureAnyCloud on Amazon EC2. The AMI is one version of the SecureAnyCloud help desk, IT service and project management listing on AWS Marketplace. You pick it as the software version when you subscribe and launch. Use it as a private chat server for a team, a community or a set of bots.

What you get:

  • TLS only from outside the instance. Clients connect to port 6697 with TLS 1.2 or 1.3, and the server refuses TLS 1.0 and 1.1. Plaintext IRC on port 6667 listens on loopback only (127.0.0.1 and [::1]) for bots and bouncers that run on the instance itself. No password ever crosses the network in clear.
  • A certificate unique to your instance. First boot creates an RSA 2048 end-entity certificate (not a CA certificate) for the instance's public DNS name and its public and private IP addresses. It records the certificate's SHA-256 fingerprint so your users can pin it. You can replace it with a Let's Encrypt or other CA certificate at any time (Step 9), and a reload picks it up without dropping anyone.
  • No default operator password. First boot generates a random 32-character IRC operator password. It stores only the bcrypt hash in the configuration and writes the password to /root/ergo-credentials.txt (root only). Upstream's example operator hash, example WEBIRC gateway, example API token and example database passwords are removed from the configuration, and ergo-guard refuses to start the server if any of them comes back.
  • A per-instance cloaking secret and a fresh database. First boot creates the account and channel database, and with it the secret Ergo uses to cloak user IP addresses. Other users see a host such as 29yhgdfawgnji.irc, never a real address. Nothing is carried over from the image.
  • Ergo 2.19.1, built from source with patched Go modules. 2.19.1 is the newest Ergo release. It fixes three advisories in 2.18 and earlier, including a SASL bypass that let a suspended or unverified account log in (GHSA-x6wg-9hcg-fc9v). SecureAnyCloud builds the v2.19.1 tag with Go 1.27.3 and newer golang.org/x/text, x/crypto and x/sys modules. This removes two reachable denial-of-service issues in the upstream release binary, and govulncheck reports nothing reachable in the shipped binary.
  • Two example values removed from the built-in default configuration. Before the build, SecureAnyCloud blanks the example OAuth2 client secret in Ergo's built-in default configuration (ergo defaultconfig), so OAuth2 stays off. It also deletes a commented example private key. Neither ships in the binary or in any configuration file.
  • Stronger defaults. Account passwords are hashed with bcrypt cost 10 (upstream's example uses 4), the HTTP API, web push and message deletion stay off, and the IP connection and login throttles stay on.
  • A sandboxed, metadata-blocked service. ergo.service runs as the unprivileged ergo user with no Linux capabilities, a read-only system (it can write only /var/lib/ergo) and a system call filter. It cannot reach the EC2 instance metadata service, and the AMI requires IMDSv2.
  • Built-in check and rotation. sudo ergo-selftest proves TLS, operator login and message delivery end to end. sudo ergo-rotate-secrets issues a new operator password and certificate in one step.
  • Fully patched. Ubuntu 24.04 LTS is patched at build time, with unattended security upgrades switched on.

The image contains:

  • Ergo 2.19.1 at /usr/local/bin/ergo, MIT license, built from the upstream tag. The licenses of every Go module it links are in /usr/local/share/doc/ergo/third-party/
  • The SecureAnyCloud tools ergo-selftest, ergo-rotate-secrets, ergo-guard and ergo-firstboot in /usr/local/sbin
  • Ubuntu 24.04 LTS, OpenSSL 3, cloud-init and the AWS Systems Manager agent

No Ubuntu package is added for Ergo itself.

Default user: ubuntu. Architecture: x86_64. Recommended instance type: m5.large.

Prerequisites

  • An AWS account subscribed to the listing, an EC2 key pair, and a VPC subnet with a route to the internet (or a private route your users connect over)
  • A security group that allows TCP 22 (SSH) from your admin network and TCP 6697 (IRC over TLS) from the networks your users connect from. Do not open 6667, because nothing listens on it outside the instance (Step 10)
  • An IRC client, for example WeeChat, irssi, HexChat, Halloy, senpai or The Lounge
1

Launch the instance

In the AWS Marketplace listing, choose Continue to Subscribe, then Continue to Configuration. Pick Ergo 2.19.1 on Ubuntu 24.04 as the software version, pick your Region, then choose Launch through EC2.

Choose m5.large, your key pair and a security group as described above. Ergo is light, and a busy network with thousands of users still fits easily.

To launch with the AWS CLI instead, replace the values in angle brackets:

aws ec2 run-instances --region <region> --image-id <ami-id-from-the-listing> \
  --instance-type m5.large --key-name <your-key-pair> \
  --security-group-ids <your-security-group> --subnet-id <your-subnet> \
  --metadata-options HttpTokens=required,HttpEndpoint=enabled \
  --tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=ergo}]'

To open the IRC port in an existing security group for one network, run the command below. It uses the documentation range 203.0.113.0/24. Use your own range, or 0.0.0.0/0 for a public chat server:

aws ec2 authorize-security-group-ingress --group-id <your-security-group> \
  --ip-permissions 'IpProtocol=tcp,FromPort=6697,ToPort=6697,IpRanges=[{CidrIp=203.0.113.0/24}]'

First boot builds the server name and the certificate from the instance's public DNS name and IP addresses. If you plan to stop and start the instance, attach an Elastic IP before first boot, or use your own DNS name and certificate (Step 9).

2

Connect to your instance

Connect over SSH as the ubuntu user (the SSH user for Ergo 2.19.1 on Ubuntu 24.04):

ssh -i <your-key.pem> ubuntu@<public-ip>

Port 22 is for you, the administrator, with your EC2 key pair. Password logins are off. Your IRC users never need SSH.

3

Check the services

First boot writes the configuration, the certificate, the operator password and the database. It then starts Ergo and runs the self test before it finishes, usually in under a minute. Check the services:

systemctl is-active ergo ergo-firstboot
sudo test -f /var/lib/sac/data/ergo-firstboot.done && echo FIRSTBOOT_DONE /usr/local/bin/ergo --version
active active FIRSTBOOT_DONE ergo-v2.19.1

Ergo listens on 6697 (TLS) on every interface and on 6667 (plaintext) on loopback only. Nothing else except SSH listens:

sudo ss -Hltnp | grep -v systemd-resolve | awk '{split($6,p,"\""); print $4, p[2]}' | sort
*:6697 ergo 0.0.0.0:22 sshd 127.0.0.1:6667 ergo [::1]:6667 ergo [::]:22 sshd

Run the built-in check. It connects over TLS to the instance's private address and verifies the certificate. It logs in as operator with the right password and confirms that a wrong password is refused. It sends a channel message from a TLS client to a loopback client, and it proves that plaintext IRC is not reachable off the instance. It prints no password:

sudo ergo-selftest
ok the served certificate matches /root/ergo-credentials.txt (<first-bytes-of-the-fingerprint>...) ok <private-ip>:6697 TLSv1.3, certificate verified against /etc/ergo/tls/fullchain.pem (IP <private-ip>), registered ok OPER with this instance's password -> 381 ok OPER with a wrong password -> 491 (refused) ok a channel message crossed from TLS 6697 to loopback plaintext 6667 (#sac-selftest-b3028c) ok plaintext <private-ip>:6667 is not reachable (loopback only) ok plaintext IRC on <private-ip>:6697 never registers (TLS only) SELFTEST_OK
4

Your server details and operator password

Only root can read the credentials file. These commands show everything in it except the operator password:

sudo grep -E '^(ERGO_OPER_NAME|ERGO_SERVER_NAME|ERGO_TLS_PORT|ERGO_VPC_IP|ERGO_PUBLIC_IP|ERGO_PUBLIC_HOSTNAME|CERT_FILE|CERT_SHA256|CERT_NAMES)=' /root/ergo-credentials.txt
sudo stat -c '%a %U %n' /root/ergo-credentials.txt
ERGO_OPER_NAME=admin ERGO_SERVER_NAME=<public-dns-name> ERGO_TLS_PORT=6697 ERGO_VPC_IP=<private-ip> ERGO_PUBLIC_IP=<public-ip> ERGO_PUBLIC_HOSTNAME=<public-dns-name> CERT_FILE=/etc/ergo/tls/fullchain.pem CERT_SHA256=<certificate-sha256-fingerprint> CERT_NAMES=IP:<private-ip>,IP:<public-ip>,DNS:<public-dns-name>,IP:127.0.0.1 600 root /root/ergo-credentials.txt
  • ERGO_SERVER_NAME is the instance's public DNS name from EC2 instance metadata (IMDSv2), or its private DNS name when the subnet gives no public one. Users connect to this name, or to any name or address in CERT_NAMES.
  • CERT_SHA256 is the fingerprint of this instance's certificate. Give it to your users so they can pin it (Step 6).

The operator password grants server administrator rights: kill, bans, account and channel administration, and reload. Print it only where nobody can read your screen, and never paste it into a channel or a ticket:

sudo grep '^ERGO_OPER_PASSWORD=' /root/ergo-credentials.txt | cut -d= -f2-

The configuration stores only a bcrypt hash of the password. ergo-guard runs before every start and reload, and it checks that the configuration holds exactly one real operator hash and none of upstream's example credentials:

sudo sed -n '/^opers:/,/^[a-z]/p' /etc/ergo/ircd.yaml | grep -cE '^ password: "\$2[aby]?\$[0-9]{2}\$'
sudo python3 -I /usr/local/lib/sac/ergo-config.py check /etc/ergo/ircd.yaml && echo CONFIG_CHECK_OK
sudo /usr/local/sbin/ergo-guard && echo GUARD_OK
1 CONFIG_CHECK_OK GUARD_OK
5

Check the TLS certificate

Ergo serves the certificate recorded in the credentials file. Compare the two:

echo | openssl s_client -connect 127.0.0.1:6697 2>/dev/null | openssl x509 -noout -fingerprint -sha256 sudo grep '^CERT_SHA256=' /root/ergo-credentials.txt
sha256 Fingerprint=<certificate-sha256-fingerprint> CERT_SHA256=<certificate-sha256-fingerprint>

It is an end-entity server certificate for this instance's names, valid for 825 days. It cannot sign other certificates:

echo | openssl s_client -connect 127.0.0.1:6697 2>/dev/null | openssl x509 -noout -subject -issuer -enddate -ext subjectAltName,basicConstraints,keyUsage,extendedKeyUsage
subject=CN = <public-dns-name>, O = sac issuer=CN = <public-dns-name>, O = sac notAfter=Feb 3 00:36:46 2029 GMT X509v3 Subject Alternative Name: IP Address:<private-ip>, IP Address:<public-ip>, DNS:<public-dns-name>, IP Address:127.0.0.1 X509v3 Basic Constraints: critical CA:FALSE X509v3 Key Usage: critical Digital Signature, Key Encipherment X509v3 Extended Key Usage: TLS Web Server Authentication

The server accepts TLS 1.2 and 1.3 and answers an older protocol with a protocol version alert:

echo | openssl s_client -connect 127.0.0.1:6697 -tls1_2 2>/dev/null | grep -E '^ *Protocol *:' {
echo | openssl s_client -connect 127.0.0.1:6697 -tls1_1 -cipher 'DEFAULT@SECLEVEL=0' 2>&1 || true; } | grep -oE 'alert protocol version' | head -1
Protocol : TLSv1.2 alert protocol version

From your own computer, check the certificate that the internet sees. Compare its fingerprint with CERT_SHA256:

openssl s_client -connect <public-dns-name>:6697 -servername <public-dns-name> </dev/null 2>/dev/null | grep -E '^(subject|issuer)=|^Protocol|Verify return code'
openssl s_client -connect <public-dns-name>:6697 -servername <public-dns-name> </dev/null 2>/dev/null | openssl x509 -noout -fingerprint -sha256
subject=CN=<public-dns-name>, O=sac issuer=CN=<public-dns-name>, O=sac Protocol: TLSv1.3 Verify return code: 18 (self-signed certificate) sha256 Fingerprint=<certificate-sha256-fingerprint>

Verify return code: 18 is expected, because no public authority signed this certificate. Do not tell clients to trust it blindly. Either pin the fingerprint in each client (Step 6), or install a CA-issued certificate (Step 9).

Run -fingerprint on its own, as shown. Some OpenSSL releases print a different value when -fingerprint and -ext are given together.

6

Connect an IRC client

Any IRC client works. Use these settings:

  • Server: ERGO_SERVER_NAME (or the public IP address)
  • Port: 6697
  • TLS / SSL: on
  • Certificate pin: CERT_SHA256, or use a CA certificate (Step 9)
  • Login: SASL PLAIN with your account name and password, once you have an account (Step 7)

Most clients that pin certificates want the fingerprint as plain lowercase hex. This command prints it in that form:

sudo sed -n 's/^CERT_SHA256=//p' /root/ergo-credentials.txt | tr -d ':' | tr 'A-F' 'a-f'
<certificate-sha256-fingerprint-as-lower-case-hex>

WeeChat (4.x), on your computer:

/server add ergo <public-dns-name>/6697 -tls /set irc.server.ergo.tls_fingerprint <certificate-sha256-fingerprint-as-lower-case-hex> /set irc.server.ergo.nicks alice /connect ergo

WeeChat checks the pin before it sends anything:

irc: connecting to server <public-ip>/6697 (TLS)... gnutls: receiving 1 certificate - subject `O=sac,CN=<public-dns-name>', issuer `O=sac,CN=<public-dns-name>', serial 0x6e04474cd3da450df7a1aac839f8d8abed903466, RSA gnutls: certificate fingerprint matches irc: connected to <public-ip>/6697 (<public-ip>) Welcome to the sac IRC Network alice Your host is <public-dns-name>, running version ergo-v2.19.1

With a wrong pin, the client stops before it logs in. This happens, for example, after ergo-rotate-secrets or if something sits in the middle of the connection:

gnutls: certificate fingerprint does NOT match (check value of option irc.server.ergo.tls_fingerprint) irc: TLS handshake failed irc: error: Error in the certificate.
7

Register an account and log in with SASL

Account registration is open by default. Anyone who can reach port 6697 can register a nickname as an account (Step 12 shows how to close registration).

Connect as the nickname you want and register it with a password of your choice:

/msg NickServ REGISTER <choose-a-password>
NickServ: Account created NickServ: You're now logged in as alice

From then on, log in during connection with SASL. The server then knows who you are before you join anything, and nobody can take your nickname. In WeeChat, keep the password in WeeChat's encrypted store:

/secure set ergo_pass <your-password> /set irc.server.ergo.sasl_mechanism plain /set irc.server.ergo.sasl_username alice /set irc.server.ergo.sasl_password "${sec.data.ergo_pass}" /set irc.server.ergo.autojoin "#sac" /reconnect ergo
irc: client capability, enabled: account-notify account-tag away-notify batch cap-notify chghost draft/multiline echo-message extended-join invite-notify message-tags multi-prefix sasl ... You are now logged in as alice Authentication successful Welcome to the sac IRC Network alice alice [alice] (~u@29yhgdfawgnji.irc) has joined #sac

SASL PLAIN is safe here because the whole connection is TLS. The exchange below comes from a scripted client. The password travels only inside the TLS session, base64 encoded:

>> CAP REQ :sasl << :<public-dns-name> CAP * ACK sasl >> AUTHENTICATE PLAIN << AUTHENTICATE + >> AUTHENTICATE <base64 of account NUL account NUL password> << :<public-dns-name> 900 * * alice :You are now logged in as alice << :<public-dns-name> 903 * :Authentication successful

A wrong password is refused, and Ergo's login throttle allows 3 attempts per minute per account:

<< :<public-dns-name> 904 * :SASL authentication failed: Invalid account credentials

Ergo also offers SCRAM-SHA-256 and EXTERNAL (TLS client certificate) logins. The server lists them as sasl=PLAIN,EXTERNAL,SCRAM-SHA-256 in its capabilities.

WHOIS confirms the login and the TLS connection, and shows other users only your cloaked host:

[alice] (~u@29yhgdfawgnji.irc): root [alice] is using a secure connection [alice] is logged in as alice

Register a channel so that your account owns it:

/join #sac /msg ChanServ REGISTER #sac
ChanServ: Channel #sac successfully registered Mode #sac [+q alice] by ChanServ
8

Become an IRC operator and moderate accounts

From your client, authenticate as operator with the password from Step 4:

/OPER admin <your-operator-password>
You are now an IRC operator User mode [+o] by <public-dns-name>

The operator name is admin in the server-admin class, which can kill and ban users, administer accounts and channels, and reload the server. WHOIS on yourself then adds the line is the server administrator.

Suspend an account. A suspended account cannot log in by any SASL method, and its clients are disconnected. This is the behavior that Ergo 2.19.1 fixes. In 2.18 and earlier, a suspended or unverified account could still log in through SCRAM and some other SASL methods (GHSA-x6wg-9hcg-fc9v):

/msg NickServ SUSPEND ADD alice DURATION 1h spam /msg NickServ SUSPEND LIST
NickServ: Successfully suspended account alice NickServ: There are 1 active account suspensions.

Logins for the suspended account then fail, both with SASL PLAIN and with SCRAM-SHA-256:

<< :<public-dns-name> 904 * :SASL authentication failed: Account has been suspended << :<public-dns-name> 904 * :Account has been suspended

/msg NickServ SUSPEND DEL alice lifts the suspension (Successfully un-suspended account alice), and the same password logs in again.

Delete an account with ERASE. It asks for a confirmation code first:

/msg NickServ ERASE alice /msg NickServ ERASE alice <code-from-the-reply> /msg NickServ INFO alice
NickServ: Warning: erasing this account will allow it to be re-registered; consider UNREGISTER instead. NickServ: To confirm, run this command: /NS ERASE alice saapi NickServ: Successfully unregistered account alice NickServ: That nickname is not registered

Drop a channel the same way with /msg ChanServ UNREGISTER #channel and the code it returns.

/msg NickServ HELP and /msg ChanServ HELP list every command. The Ergo manual, also at /usr/local/share/doc/ergo/MANUAL.md on the instance, covers UBAN, DEFCON and the rest of the moderation tools.

New operator password and certificate. ergo-rotate-secrets generates a new operator password and a new certificate for the instance's current addresses. It changes only the operator hash in the configuration, so accounts, channels and every other setting stay. It then restarts Ergo and runs the self test. Clients that pinned the old fingerprint must pin the new CERT_SHA256:

sudo ergo-rotate-secrets
ergo-rotate-secrets: new operator password and certificate in place; see /root/ergo-credentials.txt ok the served certificate matches /root/ergo-credentials.txt (<first-bytes-of-the-new-fingerprint>...) ok <private-ip>:6697 TLSv1.3, certificate verified against /etc/ergo/tls/fullchain.pem (IP <private-ip>), registered ok OPER with this instance's password -> 381 ok OPER with a wrong password -> 491 (refused) ok a channel message crossed from TLS 6697 to loopback plaintext 6667 (#sac-selftest-773582) ok plaintext <private-ip>:6667 is not reachable (loopback only) ok plaintext IRC on <private-ip>:6697 never registers (TLS only) SELFTEST_OK

If you installed a CA certificate (Step 9), note that ergo-rotate-secrets replaces it with a new self-signed one. Run your certificate deploy step again afterwards.

9

Your own DNS name and a Let's Encrypt certificate

For a server that people join from many devices, use your own DNS name with a certificate that every client trusts. Nobody then has to pin a fingerprint.

  • Attach an Elastic IP to the instance and create a DNS A record, such as irc.example.com, that points to it.
  • Get a certificate. With certbot and a DNS plugin for your DNS provider, nothing extra needs to be opened. An example is python3-certbot-dns-route53 with an instance role that may change your Route 53 zone. With the standalone HTTP challenge instead, open port 80 to the internet only while certbot runs:
sudo apt-get install -y certbot
sudo certbot certonly --standalone -d irc.example.com --agree-tos -m you@example.com --non-interactive
  • Install a deploy hook that copies the certificate to Ergo with the ownership ergo-guard requires, then reloads Ergo. Save it as /etc/letsencrypt/renewal-hooks/deploy/ergo.sh:
#!/bin/sh
# Install the renewed certificate for Ergo and reload it (no restart, no dropped clients). set -eu SRC="${RENEWED_LINEAGE:?run by certbot with RENEWED_LINEAGE set}" install -o root -g root -m 0644 "$SRC/fullchain.pem" /etc/ergo/tls/fullchain.pem.new install -o root -g ergo -m 0640 "$SRC/privkey.pem" /etc/ergo/tls/privkey.pem.new mv -f /etc/ergo/tls/fullchain.pem.new /etc/ergo/tls/fullchain.pem mv -f /etc/ergo/tls/privkey.pem.new /etc/ergo/tls/privkey.pem systemctl reload ergo
sudo chmod 0755 /etc/letsencrypt/renewal-hooks/deploy/ergo.sh
sudo RENEWED_LINEAGE=/etc/letsencrypt/live/irc.example.com /etc/letsencrypt/renewal-hooks/deploy/ergo.sh
sudo /usr/local/sbin/ergo-guard && echo GUARD_OK
sudo journalctl -u ergo --since "-1min" -o cat | grep -E 'Rehash|listening on :6697'
GUARD_OK <time> : info : server : Rehashing due to SIGHUP <time> : info : listeners : now listening on :6697, tls=true, proxy=false, tor=false, websocket=false. <time> : info : server : Rehash completed successfully

systemctl reload ergo runs ergo-guard and then asks Ergo to reload its configuration and certificates. Connected users stay connected. certbot runs the hook again after every renewal.

  • Change the server name to your DNS name. Edit the name: line in the server: section of /etc/ergo/ircd.yaml, check the file, and restart. Connected users reconnect:
sudo nano /etc/ergo/ircd.yaml
sudo /usr/local/sbin/ergo-guard && sudo systemctl restart ergo
  • From your computer, check that the certificate now verifies by name:
openssl s_client -connect irc.example.com:6697 -servername irc.example.com -verify_hostname irc.example.com </dev/null 2>/dev/null | grep -E '^subject=|Verify return code'
subject=CN=irc.example.com Verify return code: 0 (ok)

Clients can now connect to irc.example.com on port 6697 with TLS and normal certificate checking, without a pinned fingerprint. With a trusted certificate you can also turn on Ergo's STS policy (server.sts.enabled: true), which tells clients to always use TLS for this server.

ergo-selftest is written for the per-instance certificate. After you install a CA certificate, it stops at its first check (SELFTEST_FAIL: the certificate does not match CERT_SHA256 in /root/ergo-credentials.txt). Check the server with the openssl s_client command above instead.

10

Why plaintext port 6667 is loopback only

Plaintext IRC sends everything in clear text, including SASL and OPER passwords and every private message. On this image, Ergo's plaintext listener is bound to 127.0.0.1:6667 and [::1]:6667 only (Step 3). It serves bots, bridges and bouncers running on the instance itself, and nothing else can reach it, whatever the security group says. ergo-selftest proves that a plaintext connection to the instance's VPC address fails.

You may have a legacy client or device that cannot do TLS and connects over a network you fully trust, for example inside your VPC or over a VPN. In that case you can expose 6667 deliberately. Replace the two loopback listener lines with one listener on every interface, and reload:

sudo cp -p /etc/ergo/ircd.yaml /root/ircd.yaml.before-6667
sudo sed -i -e 's/^ "127.0.0.1:6667":.*/ ":6667": # plaintext on every interface/' -e '/^ "\[::1\]:6667":/d' /etc/ergo/ircd.yaml
sudo systemctl reload ergo
sudo ss -Hltn 'sport = :6667'
LISTEN 0 128 *:6667 *:*

Ergo itself warns about it in its log:

<time> : warn : listeners : Warning: your server is configured with public plaintext listener :6667. Consider disabling it for improved security and privacy.

Warning: anyone who can see that traffic can read every password and message on it. Add a security group rule for 6667 only from the exact private addresses that need it, never from 0.0.0.0/0. Consider adding those networks to server.secure-nets instead of exposing more.

To go back, restore the file and reload. Ergo returns to loopback only:

sudo cp -p /root/ircd.yaml.before-6667 /etc/ergo/ircd.yaml && sudo rm -f /root/ircd.yaml.before-6667
sudo systemctl reload ergo
sudo ss -Hltn 'sport = :6667'
LISTEN 0 128 127.0.0.1:6667 0.0.0.0:* LISTEN 0 128 [::1]:6667 [::]:*
11

Security checks

The instance metadata service is blocked for Ergo. Ergo can fetch URLs that users supply, for example web push endpoints if you turn web push on. A systemd IPAddressDeny drop-in in /etc/systemd/system/ergo.service.d/ therefore denies the service the EC2 metadata addresses at the kernel level. Your own SSH shell still reaches IMDSv2, while a process inside the ergo.service cgroup cannot:

systemctl show -p IPAddressDeny ergo.service echo "from your SSH shell: $(curl -s -m 5 -o /dev/null -w '%{http_code}' -X PUT -H 'X-aws-ec2-metadata-token-ttl-seconds: 60' http://169.254.169.254/latest/api/token)" CG="$(systemctl show -p ControlGroup --value ergo.service)" echo "from inside ergo.service: $(sudo bash -c 'echo $$ > "/sys/fs/cgroup$1/cgroup.procs"; exec curl -s -m 5 -o /dev/null -w "%{http_code}" -X PUT -H "X-aws-ec2-metadata-token-ttl-seconds: 60" http://169.254.169.254/latest/api/token' _ "$CG")"
IPAddressDeny=168.63.129.16/32 169.254.169.254/32 fd00:ec2::254/128 from your SSH shell: 200 from inside ergo.service: 000

The service sandbox:

systemctl show ergo.service -p User -p NoNewPrivileges -p CapabilityBoundingSet -p ProtectSystem -p ReadWritePaths -p MemoryDenyWriteExecute -p PrivateDevices
CapabilityBoundingSet= User=ergo ReadWritePaths=/var/lib/ergo PrivateDevices=yes ProtectSystem=strict NoNewPrivileges=yes MemoryDenyWriteExecute=yes

Features that fetch or expose more stay off until you decide otherwise. These are the HTTP API, which would listen on 127.0.0.1:8089 and needs a bearer token you create, and web push:

sudo python3 -I /usr/local/lib/sac/ergo-config.py effective /etc/ergo/ircd.yaml | grep -A1 -E '^(webpush|api):'
webpush: enabled: false -- api: enabled: false

What ergo-guard enforces before every start and reload: /etc/ergo is root:root 0755; /etc/ergo/ircd.yaml is a root:ergo 0640 regular file; /etc/ergo/tls is root:ergo 0750; the private key is a root:ergo file no one else can read; /var/lib/ergo is ergo:ergo 0700 and ircd.db an ergo-owned 0600 file; none of them is a symbolic link; and the configuration holds one bcrypt operator hash and no example credential. If any check fails, Ergo does not start (or keeps running the old configuration on a reload) and journalctl -u ergo names the reason.

12

Configuration, data, history and backups

The configuration is /etc/ergo/ircd.yaml, which is upstream's documented example with SecureAnyCloud's changes. After an edit, run sudo /usr/local/sbin/ergo-guard and then sudo systemctl reload ergo. Most settings apply on reload.

The server name, the network name (network.name, SecureAnyCloud by default) and the database path need sudo systemctl restart ergo. The message of the day is /etc/ergo/ergo.motd, which holds upstream's sample text. Replace it with your own and reload.

Useful settings for a private server:

  • accounts.registration.enabled: false stops self-registration. An operator then creates accounts on a user's behalf with /msg NickServ SAREGISTER <account> <password>.
  • accounts.require-sasl.enabled: true admits only clients that log in with SASL ("SASL-only mode" in the Ergo manual), which keeps out everyone without an account.
  • channels.registration.operator-only: true lets only operators register channels.

Accounts, channels and the cloaking secret live in one file, /var/lib/ergo/ircd.db, owned by ergo and readable by no one else:

sudo ls -l /var/lib/ergo
sudo python3 -I /usr/local/lib/sac/ergo-config.py effective /etc/ergo/ircd.yaml | sed -n '/^history:/,/^allow-environment/p' | grep -E '^history:|^ enabled:|^ channel-length:|^ client-length:|^ persistent:|^ enabled:'
total 4 -rw------- 1 ergo ergo 2812 Nov 1 00:33 ircd.db -rw------- 1 ergo ergo 0 Nov 1 00:16 ircd.lock history: enabled: true channel-length: 2048 client-length: 256 persistent: enabled: false

Message history is on and kept in memory. Ergo keeps the last 2048 messages per channel and 256 per user, for up to a week. Clients with CHATHISTORY support, and bouncers, replay them when they reconnect. A restart or reboot clears the history.

Persistent history needs a MySQL server (datastore.mysql and history.persistent in the configuration). This image does not include one.

Back up the database. The file holds every account (with bcrypt password hashes) and the instance's cloaking secret, so treat backups as secrets. For a consistent copy, stop Ergo for a moment. Connected users reconnect:

sudo systemctl stop ergo
sudo install -d -m 700 /var/backups/ergo
sudo cp -p /var/lib/ergo/ircd.db /var/backups/ergo/ircd-$(date -u +%Y%m%dT%H%M%SZ).db
sudo systemctl start ergo
sudo ls -l /var/backups/ergo
total 4 -rw------- 1 ergo ergo 2512 Nov 1 00:27 ircd-20261101T003041Z.db

Copy the backup off the instance, for example to an encrypted S3 bucket, or take EBS snapshots with AWS Backup.

Restore by stopping Ergo and writing the file back as the ergo user, so that root never writes inside Ergo's data directory. Then start Ergo again:

sudo systemctl stop ergo
sudo cat /var/backups/ergo/ircd-20261101T003041Z.db | sudo -u ergo sh -c 'umask 077; cat > /var/lib/ergo/ircd.db'
sudo ls -l /var/lib/ergo/ircd.db
sudo systemctl start ergo
-rw------- 1 ergo ergo 2512 Nov 1 00:32 /var/lib/ergo/ircd.db

An account erased after the backup was taken is back after the restore. It logs in again with its old password (903 :Authentication successful).

13

Updates and upgrades

Ubuntu security updates install automatically, because unattended-upgrades is on. Ergo itself is not an Ubuntu package. It is the binary that SecureAnyCloud built from the upstream tag:

cat /var/lib/sac/data/ergo-build-versions systemctl is-enabled unattended-upgrades sha256sum /usr/local/bin/ergo
ergo=2.19.1 (source build go1.27.3, tags i18n mysql postgresql sqlite, raises golang.org/x/text@v0.43.0 golang.org/x/crypto@v0.58.0 golang.org/x/sys@v0.49.0) enabled fb29a99ba32e3fa2bb80682ca0e5996f2172b4fef3c40088f7419f4e26a073ec /usr/local/bin/ergo

The build is documented in /usr/local/share/doc/ergo/BUILD-PROVENANCE.txt and ergo-dependency-raises.diff. They cover the source archive and toolchain checksums, the two-line edit to the built-in default configuration, the module changes and the build flags.

To move to a newer Ergo release:

  • Recommended: launch the newer version from the listing when SecureAnyCloud publishes it, and move your data across. Copy /var/lib/ergo/ircd.db (Step 12) and carry over your configuration changes. Ergo upgrades the database format automatically on start (datastore.autoupgrade: true).
  • In place: back up the database first (Step 12). Download the new linux-x86_64 release from Ergo's releases page and verify it against the release checksums. Then replace the binary and restart. That binary is the Ergo project's own build, so it does not carry SecureAnyCloud's Go module updates. Check its release notes for security fixes:
sudo install -o root -g root -m 0755 ergo /usr/local/bin/ergo
sudo systemctl restart ergo
sudo ergo-selftest

Read the release notes first. A new release can add or rename configuration keys, which you merge into /etc/ergo/ircd.yaml by hand.

14

Known limitations

  • No web interface. Ergo is an IRC server only. For browser users, run a web client such as The Lounge or Kiwi IRC elsewhere and point it at port 6697.
  • Open registration by default. Anyone who can reach port 6697 can register an account. Keep the security group as narrow as your users allow, or turn on the settings in Step 12.
  • Self signed certificate until you replace it. Users must pin CERT_SHA256 or you install a CA certificate (Step 9). The certificate names the public DNS name and IP addresses from first boot: after a stop and start without an Elastic IP the public address changes, and sudo ergo-rotate-secrets issues a certificate for the new one.
  • ergo-selftest and ergo-rotate-secrets assume the per instance certificate. With a CA certificate installed the self test fails its first check, and a rotation replaces your CA certificate (Steps 8 and 9).
  • History is kept in memory only and is lost on restart (Step 12).
  • The cloaking secret is fixed for the life of the database. No SecureAnyCloud tool changes it, because changing it would change every cloaked host name and invalidate bans; it is unique to your instance and never in the image.
  • The message of the day is upstream's sample text until you replace /etc/ergo/ergo.motd.
  • One unfixed Go advisory at module level. govulncheck lists GO-2026-5932 for the unmaintained golang.org/x/crypto/openpgp package, which has no fix. Ergo does not import or link that package, so it cannot be reached.
  • No WebSocket, Tor or STS listener is configured; add them in /etc/ergo/ircd.yaml if you need them (STS only with a CA certificate).

Troubleshooting

  • ergo.service does not start: journalctl -u ergo -n 50 --no-pager shows the reason. An ergo-guard: FATAL line names the file, owner or mode to fix. sudo /usr/local/sbin/ergo-guard runs the same checks by hand.
  • First boot did not finish: check sudo systemctl status ergo-firstboot and /var/log/sac/sac-firstboot.log. By design, Ergo stays stopped until first boot succeeds.
  • Clients time out: check that the security group allows TCP 6697 from the client's address. Check also that the client uses TLS on 6697, because a plaintext client on 6697 never registers.
  • Certificate errors: compare the client's fingerprint with CERT_SHA256 (Step 5). After ergo-rotate-secrets, every client must pin the new value.
  • Nickname is reserved by a different account: the nickname belongs to an account. Log in with SASL as that account, or pick another nickname.
  • Too many connections from one address: Ergo allows 16 concurrent connections and 32 new connections per 10 minutes per IPv4 address (server.ip-limits). Raise the limits, or add exemptions for a known gateway.

Security recommendations

  • Allow port 22 from your admin network only, and port 6697 only from the networks your users connect from. Never open 6667 to the internet.
  • Use your own DNS name and a CA certificate (Step 9), or make sure every user pins CERT_SHA256.
  • Keep the operator password only in /root/ergo-credentials.txt and a password manager. Rotate it with sudo ergo-rotate-secrets if it may have been seen.
  • For a private server, turn off self-registration and require SASL (Step 12).
  • Back up /var/lib/ergo/ircd.db regularly and keep the backups private (Step 12).
  • Keep the instance patched. unattended-upgrades handles Ubuntu, and new Ergo releases come as new versions of this listing.

Support and licensing

Ergo is open source under the MIT license and is developed by the ergochat contributors (github.com/ergochat/ergo). There is no per-user or per-CPU license fee. The AMI price covers SecureAnyCloud's source build, hardening, first-boot setup and support. SecureAnyCloud is not affiliated with, endorsed by or sponsored by the Ergo project or its contributors.

  • Email: sales@secureanycloud.com
  • Website: https://secureanycloud.com
  • Support: 24/7, with a 24-hour response SLA

All product and company names are trademarks or registered trademarks of their respective holders. Use of them does not imply any affiliation with or endorsement by them.

Deploy on AWS

Launch Ergo 2.19.1 on Ubuntu 24.04 on AWS with 24/7 support from SecureAnyCloud.

View on Marketplace

Need Help?

Our support team is available 24/7.

sales@secureanycloud.com

Deploy on AWS