Ergo 2.19.1 on AWS User Guide
Overview
This guide covers launching and using the Ergo 2.19.1 on Ubuntu 24.04 AMI from cloudimg on Amazon EC2. It is one version of the cloudimg help desk, IT service and project management listing on AWS Marketplace; you pick it as the software version when you subscribe and launch.
Ergo is a modern IRC server written in Go. Where a classic IRC daemon needs a separate "services" package, Ergo has user accounts (NickServ), channel registration (ChanServ), message history, multiple devices per account, IRCv3 and SASL login built in, all in one binary with one configuration file. Use it as a private chat server for a team, a community or a set of bots, with any IRC client. Ergo has no web interface: you and your users connect with an IRC client.
What the cloudimg image adds:
- TLS only from outside the instance. Clients connect to port 6697 with TLS 1.2 or 1.3; TLS 1.0 and 1.1 are refused by the server. Plaintext IRC on port 6667 listens on loopback only (
127.0.0.1and[::1]), for bots and bouncers that run on the instance itself, so 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, and records its SHA-256 fingerprint so your users can pin it. Replace it with a Let's Encrypt or other CA certificate whenever you like (Step 9); a reload picks it up without dropping anyone.
- No default operator password. First boot generates a random 32 character IRC operator password, stores only its 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, andergo-guardrefuses to start the server if any of them comes back. - Per instance cloaking secret and a fresh database. The account and channel database is created on first boot, 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 and 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). cloudimg builds the
v2.19.1tag with Go 1.27.3 and newergolang.org/x/text,x/cryptoandx/sysmodules, which removes two reachable denial of service issues in the upstream release binary;govulncheckreports nothing reachable in the shipped binary. Before the build, cloudimg also removes two example values from Ergo's built-in default configuration (ergo defaultconfig): the example OAuth2 client secret is blank (OAuth2 stays off) and a commented example private key is deleted, so 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.serviceruns as the unprivilegedergouser 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-selftestproves TLS, operator login and message delivery end to end;sudo ergo-rotate-secretsissues a new operator password and certificate in one step. - Fully patched Ubuntu 24.04 LTS at build time, with unattended security upgrades switched on.
What's on the image: Ergo 2.19.1 (/usr/local/bin/ergo, MIT licence, built from the upstream tag; the licences of every Go module it links are in /usr/local/share/doc/ergo/third-party/), the cloudimg 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: nothing listens on it outside the instance (Step 10).
- An IRC client: for example WeeChat, irssi, HexChat, Halloy, senpai or The Lounge.
Step 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 and your Region, then Launch through EC2. Choose m5.large (Ergo is light; a busy network with thousands of users still fits easily), your key pair and a security group as above.
With the AWS CLI (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 (here the documentation range 203.0.113.0/24; use your own, 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}]'
The server name and the certificate are made from the instance's public DNS name and IP addresses at first boot. 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).
Step 2: Connect to your instance
| Version | SSH user |
|---|---|
| Ergo 2.19.1 on Ubuntu 24.04 | ubuntu |
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.
Step 3: Check the services
First boot writes the configuration, the certificate, the operator password and the database, starts Ergo and runs the self test before it finishes, usually in under a minute:
systemctl is-active ergo ergo-firstboot
sudo test -f /var/lib/cloudimg/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 but 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, logs in as operator with the right password and is refused with a wrong one, sends a channel message from a TLS client to a loopback client, and 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 (#cloudimg-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
Step 4: Your server details and operator password
The credentials file is readable by root only. This shows 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_NAMEis 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 it, or to any name or address inCERT_NAMES.CERT_SHA256is 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, reload). To print it, do this 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 it, and ergo-guard (run before every start and reload) 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/cloudimg/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
Step 5: Check the TLS certificate
The certificate Ergo serves is the one recorded in the credentials file:
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, that 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 = cloudimg
issuer=CN = <public-dns-name>, O = cloudimg
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
TLS 1.2 and 1.3 are accepted; the server 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 the internet sees and compare the 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=cloudimg
issuer=CN=<public-dns-name>, O=cloudimg
Protocol: TLSv1.3
Verify return code: 18 (self-signed certificate)
sha256 Fingerprint=<certificate-sha256-fingerprint>
Verify return code: 18 is expected: 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.
Step 6: Connect an IRC client
Any IRC client works. Use these settings:
| Setting | Value |
|---|---|
| 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 lower case hex. This 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=cloudimg,CN=<public-dns-name>', issuer `O=cloudimg,CN=<public-dns-name>', serial 0x6e04474cd3da450df7a1aac839f8d8abed903466, RSA
gnutls: certificate fingerprint matches
irc: connected to <public-ip>/6697 (<public-ip>)
Welcome to the cloudimg IRC Network alice
Your host is <public-dns-name>, running version ergo-v2.19.1
With a wrong pin (for example after ergo-rotate-secrets, or if something sits in the middle), the client stops before it logs in:
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.
Step 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 it). 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, so the server 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 "#cloudimg"
/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 cloudimg IRC Network alice
alice [alice] (~u@29yhgdfawgnji.irc) has joined #cloudimg
SASL PLAIN is safe here because the whole connection is TLS. On the wire the exchange looks like this (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 #cloudimg
/msg ChanServ REGISTER #cloudimg
ChanServ: Channel #cloudimg successfully registered
Mode #cloudimg [+q alice] by ChanServ
Step 8: Become an IRC operator, and moderate accounts
From your client, 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. That is exactly what 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, which 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
A channel is dropped 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, changes only the operator hash in the configuration (accounts, channels and every other setting stay), 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 (#cloudimg-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.
Step 9: Your own DNS name and a Let's Encrypt certificate
For a server people join from many devices, use your own DNS name with a certificate every client trusts, so nobody has to pin a fingerprint.
- Attach an Elastic IP to the instance and create a DNS
Arecord such asirc.example.compointing to it. - Get a certificate. With certbot and a DNS plugin for your DNS provider (for example
python3-certbot-dns-route53with an instance role that may change your Route 53 zone), nothing extra needs to be opened. 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-guardrequires and 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 theserver:section of/etc/ergo/ircd.yaml, check it 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 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.
Step 10: Why plaintext port 6667 is loopback only
Plaintext IRC sends everything, including SASL and OPER passwords and every private message, in clear text. On this image Ergo's plaintext listener is bound to 127.0.0.1:6667 and [::1]:6667 only (Step 3), so it serves bots, bridges and bouncers running on the instance itself and cannot be reached from anywhere else, whatever the security group says. ergo-selftest proves that a plaintext connection to the instance's VPC address fails.
If you have a legacy client or device that cannot do TLS, and it connects over a network you fully trust (for example inside your VPC or over a VPN), 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. Only add a security group rule for 6667 from the exact private addresses that need it, never from 0.0.0.0/0, and 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 [::]:*
Step 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), so the service is denied the EC2 metadata addresses at the kernel level by a systemd IPAddressDeny drop-in in /etc/systemd/system/ergo.service.d/. 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: the HTTP API (it would listen on 127.0.0.1:8089 and needs a bearer token you create) and web push:
sudo python3 -I /usr/local/lib/cloudimg/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.
Step 12: Configuration, data, history and backups
The configuration is /etc/ergo/ircd.yaml (upstream's documented example, with cloudimg'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, cloudimg by default) and the database path need sudo systemctl restart ergo. The message of the day is /etc/ergo/ergo.motd (upstream's sample text; replace it with your own and reload).
Useful settings for a private server:
accounts.registration.enabled: falsestops self registration; an operator then creates accounts on a user's behalf with/msg NickServ SAREGISTER <account> <password>.accounts.require-sasl.enabled: trueadmits 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: truelets 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/cloudimg/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: the last 2048 messages per channel and 256 per user, for up to a week, which clients with CHATHISTORY support (and bouncers) replay when they reconnect. A restart or reboot clears it. 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 S3 bucket with encryption), or take EBS snapshots with AWS Backup. Restore by stopping Ergo and writing the file back as the ergo user (so root never writes inside Ergo's data directory), then starting it 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 and logs in again with its old password (903 :Authentication successful).
Step 13: Updates and upgrades
Ubuntu security updates install automatically (unattended-upgrades is on). Ergo itself is not an Ubuntu package; it is the binary cloudimg built from the upstream tag:
cat /var/lib/cloudimg/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
How the binary was built (source archive and toolchain checksums, the two-line edit to the built-in default configuration, the module changes and the build flags) is in /usr/local/share/doc/ergo/BUILD-PROVENANCE.txt and ergo-dependency-raises.diff.
To move to a newer Ergo release:
- Recommended: launch the newer version from the listing when cloudimg 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_64release from Ergo's releases page, 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 cloudimg'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.
Step 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_SHA256or 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, andsudo ergo-rotate-secretsissues a certificate for the new one. ergo-selftestandergo-rotate-secretsassume 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 cloudimg 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.
govulnchecklists GO-2026-5932 for the unmaintainedgolang.org/x/crypto/openpgppackage, 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.yamlif you need them (STS only with a CA certificate).
Troubleshooting
ergo.servicedoes not start:journalctl -u ergo -n 50 --no-pagershows the reason; anergo-guard: FATALline names the file, owner or mode to fix.sudo /usr/local/sbin/ergo-guardruns the same checks by hand.- First boot did not finish:
sudo systemctl status ergo-firstbootand/var/log/cloudimg-firstboot.log. Ergo stays stopped until first boot succeeds, by design. - Clients time out: check that the security group allows TCP 6697 from the client's address, and that the client uses TLS on 6697 (a plaintext client on 6697 never registers).
- Certificate errors: compare the client's fingerprint with
CERT_SHA256(Step 5); afterergo-rotate-secretsevery 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 them 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 in
/root/ergo-credentials.txtand a password manager only; rotate it withsudo ergo-rotate-secretsif it may have been seen. - For a private server, turn off self registration and require SASL (Step 12).
- Back up
/var/lib/ergo/ircd.dbregularly 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 licence, developed by the ergochat contributors (github.com/ergochat/ergo); there is no per user or per CPU licence fee. The AMI price covers cloudimg's source build, hardening, first boot setup and support. cloudimg is not affiliated with, endorsed by or sponsored by the Ergo project or its contributors.
- Email: support@cloudimg.co.uk
- Website: https://www.cloudimg.co.uk
- 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.