Ech0
Overview
Ech0 is a free, open-source, self-hosted publishing platform for the flow of personal thoughts. You post short notes, links, images and audio to a public timeline that you run yourself, instead of handing your ideas to a third-party feed that can change its rules.
This SecureAnyCloud image delivers Ech0 fully installed and configured on Ubuntu 24.04. A single, statically linked Go binary serves both the web interface and the REST API, backed by a self-contained SQLite database. There is no separate application server, web server or database to operate, so a working timeline is running within minutes of launch.
Ech0 is licensed under the GNU Affero General Public License v3.0 or later (AGPL-3.0-or-later). This image repackages the upstream open-source release, unmodified, with SecureAnyCloud's provisioning and support. All product and company names are trademarks or registered trademarks of their respective holders.
What you get:
- Ech0 5.4.6 (AGPL-3.0-or-later), installed at
/opt/ech0 - A single statically linked Go binary that serves the web UI and the REST API on port
6277, with the compiled frontend embedded in the binary - Markdown posting, a public feed, RSS output, search and a posting activity heatmap
- Optional OpenID Connect single sign-on or WebAuthn passkeys for sign-in, and a documented REST API with personal access tokens for automation
- Self-contained SQLite storage under
/opt/ech0/data, so there is no separate database server to run or secure - A unique administrator password and a unique authentication token signing secret, both generated on first boot and written to a root-only file. No default or shared login and no shared signing key ship in the image
- Public self-registration closed by default, so nobody who reaches your instance can claim it before you do
ech0.servicemanaged by systemd, enabled and active, and gated so it starts only once first boot has provisioned the per-VM secret- 24/7 SecureAnyCloud support
Prerequisites
You need an active Azure subscription, an SSH key pair, and a VNet with a subnet in the target region.
Standard_B2s (2 vCPU / 4 GiB RAM) is a good starting size and is ample for personal or small-team use.
Configure the network security group (NSG) to allow inbound 22/tcp from your management network and 6277/tcp from your users. Add 443/tcp if you put Ech0 behind HTTPS (Step 11).
Step 1 - Deploy from the Azure Marketplace
Sign in to the Azure Portal, choose Create a resource, search the Marketplace for Ech0 by SecureAnyCloud, and select Create.
On the Basics tab, pick your subscription, resource group, region and size. Under Administrator account, choose SSH public key and paste your key. Under Inbound port rules, allow SSH (22) and add a custom rule for 6277.
Select Review + create, then Create.
Step 2 - Deploy from the Azure CLI
# Accept the Marketplace terms once per subscription (required before the first CLI deploy)
az vm image terms accept --urn sac1647283583153:ech0-ubuntu-24-04:default:latest
az vm create \
--resource-group <your-rg> \
--name ech0 \
--image sac1647283583153:ech0-ubuntu-24-04:default:latest \
--size Standard_B2s \
--admin-username azureuser \
--generate-ssh-keys \
--public-ip-sku StandardOpen the Ech0 port to reach the web UI:
az vm open-port --resource-group <your-rg> --name ech0 --port 6277 --priority 900Step 3 - Connect to your VM
ssh azureuser@<vm-public-ip>Step 4 - Confirm the service is running
Ech0 runs as a single systemd service on port 6277. The unit is enabled, so it restarts automatically after a reboot. It is deliberately gated to start only once first boot has generated the per-VM signing secret.
systemctl is-active ech0.service ech0-firstboot.serviceCheck the version in this image and confirm that it is listening on 6277:
/opt/ech0/ech0 version 2>/dev/null | tail -1 ss -tlnH | awk '{print $4}' | grep 6277Confirm that the application answers on the loopback interface. /healthz is the built-in health endpoint:
curl -s -o /dev/null -w 'health %{http_code}\n' http://127.0.0.1:6277/healthz
curl -s -o /dev/null -w 'app %{http_code}\n' http://127.0.0.1:6277/Step 5 - Retrieve your per-VM administrator credentials
On the first boot of each VM, ech0-firstboot.service generates a unique administrator password and a unique JWT signing secret. It creates the administrator account and writes the login to a root-only file. Nothing is shared between deployments and nothing is baked into the image.
sudo cat /root/ech0-credentials.txtOnly root can read the credentials file and the environment file that holds the per-VM signing secret:
sudo stat -c '%a %U:%G %n' /root/ech0-credentials.txt /opt/ech0/ech0.envBack up /opt/ech0/ech0.env. It holds the per-VM JWT_SECRET that signs your login tokens. Also back up /opt/ech0/data, which contains the SQLite database and any uploaded media.
Step 6 - Browse your timeline and sign in
Browse to http://<vm-public-ip>:6277/. The public timeline is what visitors see. A fresh instance starts empty and fills as you post.
Select Login and sign in with the username and password from the credentials file. That account is the administrator (owner) of the installation.
There is deliberately no open Register flow. Public self-registration is closed on this image so that nobody who reaches your instance can create an account before you do. Step 10 explains how to reopen it or add users directly.
Step 7 - Publish your first post
While signed in, select Publish. Write your note in Markdown, optionally attach an image or audio clip or add a tag, and publish it to your timeline.
You can also confirm the whole publish path from the shell, with no browser. The commands below use the per-VM administrator credentials the image already wrote to disk, log in, and read back the number of posts on the public timeline:
B=http://127.0.0.1:6277
U=$(sudo grep '^ech0.admin.user=' /root/ech0-credentials.txt | cut -d= -f2-)
P=$(sudo grep '^ech0.admin.pass=' /root/ech0-credentials.txt | cut -d= -f2-)
TOKEN=$(curl -s -H 'Content-Type: application/json' -X POST \
--data "{\"username\":\"$U\",\"password\":\"$P\"}" $B/api/login \
| python3 -c 'import json,sys; print(json.load(sys.stdin)["data"]["access_token"])')
echo -n "login token acquired: "; [ -n "$TOKEN" ] && echo yes || echo no curl -s -X POST -H 'Content-Type: application/json' --data '{"page":1,"pageSize":10}' \
$B/api/echo/page | python3 -c 'import json,sys; d=json.load(sys.stdin).get("data") or {}; print("posts on the public timeline:", d.get("total"))'Step 8 - Check health and use the REST API
Every operation in the UI is also available over a documented REST API. The public endpoints need no token:
curl -s -o /dev/null -w 'GET /healthz -> HTTP %{http_code}\n' http://127.0.0.1:6277/healthz
curl -s http://127.0.0.1:6277/api/init/statusCreate a personal access token in the admin panel under Settings. Then pass it as a bearer token from your own machine:
curl -s -H "Authorization: Bearer <your-personal-access-token>" \
http://<vm-public-ip>:6277/api/echo/page -X POST \
-H 'Content-Type: application/json' --data '{"page":1,"pageSize":20}'Step 9 - Review the security model
The image is hardened so that a freshly launched instance is safe before you touch it. Upstream Ech0 has two defaults that are unsafe on a public VM, and this image closes both:
- No baked signing secret. Upstream's quick-start signs authentication tokens with a well-known default (
JWT_SECRET="Hello Echos"), which would let anyone forge an administrator token. This image bakes in no secret. First boot generates a unique 64-hex secret per instance. - No first-registered-becomes-admin window. Upstream makes the first account to register the administrator. This image creates the administrator on first boot, so the owner slot is already taken, and it closes public registration.
- Only SSH and Ech0 listen. SQLite is a local file, so there is no database port to expose.
You can verify all of this on your own instance. The first script logs in with the per-VM administrator credentials. It then presents a token whose header and payload are copied from a valid login token but re-signed with the upstream default "Hello Echos" secret. The token is rejected, which shows that the running secret is not the default:
B=http://127.0.0.1:6277
sudo grep -q 'JWT_SECRET=<set-at-first-boot> Echos' /opt/ech0/ech0.env && echo "UPSTREAM DEFAULT (unsafe)" || echo "signing secret is unique to this instance (not the upstream default)"
U=$(sudo grep '^ech0.admin.user=' /root/ech0-credentials.txt | cut -d= -f2-)
P=$(sudo grep '^ech0.admin.pass=' /root/ech0-credentials.txt | cut -d= -f2-)
T=$(curl -s -H 'Content-Type: application/json' -X POST --data "{\"username\":\"$U\",\"password\":\"$P\"}" $B/api/login | python3 -c 'import json,sys; print(json.load(sys.stdin)["data"]["access_token"])')
HP=$(printf %s "$T" | cut -d. -f1,2)
SIG=$(printf %s "$HP" | openssl dgst -binary -sha256 -hmac 'Hello Echos' | openssl base64 -A | tr '+/' '-_' | tr -d '=')
curl -s -o /dev/null -w 'token forged with the upstream default secret -> HTTP %{http_code} (rejected)\n' -H "Authorization: Bearer $HP.$SIG" $B/api/userThis second script shows that public self-registration creates no usable account:
B=http://127.0.0.1:6277 curl -s -o /dev/null -w 'register attempt -> HTTP %{http_code}; ' -H 'Content-Type: application/json' -X POST --data '{"username":"stranger","password":"StrangerPass-123","email":"s@x.local"}' $B/api/register curl -s -o /dev/null -w 'then login as that user -> HTTP %{http_code} (refused, no account created)\n' -H 'Content-Type: application/json' -X POST --data '{"username":"stranger","password":"StrangerPass-123"}' $B/api/login ss -tlnH | awk '{print $4}' | sort -uStep 10 - Manage users and reopen registration
To add people, sign in as the administrator and open the admin panel under Settings. Under Users, create accounts directly. This is the recommended approach.
To let people sign themselves up instead, turn on the Allow registration switch to reopen public self-registration. Only administrators can publish to the timeline.
Step 11 - Put Ech0 behind HTTPS with a custom domain
Ech0 serves plain HTTP on 6277. For a public site, front it with a reverse proxy that terminates TLS. With Caddy, a two-line Caddyfile gets you an automatic certificate:
your-domain.example.com { reverse_proxy 127.0.0.1:6277 }Then set Ech0's public address so that links, RSS and share URLs are correct, and restart the service:
sudo sed -i 's#^ECH0_SETTING_SERVER_URL=.*#ECH0_SETTING_SERVER_URL=https://your-domain.example.com#' /opt/ech0/ech0.env
sudo systemctl restart ech0.serviceStep 12 - Back up and restore
All state lives in two places: the environment file with the signing secret, and the data directory with the SQLite database and uploads.
sudo ls -la /opt/ech0/ech0.env /opt/ech0/dataTo back up, copy /opt/ech0/ech0.env and the whole /opt/ech0/data directory somewhere safe.
To restore onto a new VM, stop ech0.service, copy both back, and start the service again.
Step 13 - Maintenance and updates
The image is patched at build time and keeps receiving Ubuntu security updates automatically.
To move to a newer Ech0 release, download the new ech0-linux-amd64 binary from the upstream releases, replace /opt/ech0/ech0, and restart the service. Your data and per-VM secret are untouched.
Support
SecureAnyCloud provides 24/7 technical support for this Ech0 product by email (sales@secureanycloud.com) and live chat. We help with deployment, retrieving first-boot credentials, publishing, REST API and personal access token setup, single sign-on and passkeys, opening or closing registration, HTTPS and custom domains, backups, upgrades and troubleshooting.
Critical issues receive a one-hour average response time.
Deploy on Azure
Launch Ech0 on Ubuntu 24.04 LTS on Azure with 24/7 support from SecureAnyCloud.
View this image
Need Help?
Our support team is available 24/7.
sales@secureanycloud.com