Euterpe 1.6.3 on AWS User Guide
Overview
This guide covers launching and verifying Euterpe 1.6.3 on Ubuntu 24.04 from cloudimg on Amazon EC2. It is one version of the cloudimg Hardened SRS Streaming Media Server & Media Servers AMIs listing on AWS Marketplace; you pick it as the software version when you subscribe and launch. The other media and streaming products in the listing are described on the listing's guide hub.
Euterpe is an open-source, self-hosted music streaming server (GNU GPL v3). It indexes the music files you put on the instance, reads their tags and cover art, and streams them to a web player in your browser, to scripts through a documented REST API, and to mobile players through a Subsonic-compatible API. Your collection stays on infrastructure you control.
What is on the image:
- Euterpe 1.6.3, compiled by cloudimg from the upstream
v1.6.3source archive (commit1a3bf307) with Go 1.27.3, linked against Ubuntu's TagLib and ICU libraries - Three Go libraries raised to fixed releases (golang.org/x/image 0.47.0, golang.org/x/text 0.43.0, go-sqlite3 1.14.52 with SQLite 3.53.4); see Security posture
- Euterpe on
127.0.0.1:9996only, behind nginx serving HTTPS on port 443 with a certificate created on your instance; port 80 redirects to HTTPS - A per-instance admin password and token-signing secret, created on first boot
- An empty music library at
/var/lib/euterpe/music: no recording of any kind is included euterpe-set-hostname(serve under your own DNS name),euterpe-rotate-passwordandeuterpe-selftest(an end-to-end check)
Default user: ubuntu · Architecture: x86_64 · Root volume: 8 GiB gp3
Security posture
- Sign-in on every surface, no default login. First boot generates the admin password and the secret that signs API tokens and browser sessions on your instance and writes them to a root-only file. The web player, the REST API (by password, by API token or by browser session) and the Subsonic API all require them;
admin:adminand other guesses are refused. Neither value appears on a command line or in a log. - Fails closed. Upstream Euterpe writes itself a configuration with authentication switched off when its configuration file is missing. This image closes that path three ways: the Euterpe service will not start without the configuration first boot writes and a root-owned marker, and a guard refuses any configuration without authentication, the loopback address and a per-instance password and secret; the service account cannot write that fallback configuration anywhere (its home directory is read-only and root-owned); and nothing listens on the network until first boot has finished.
- HTTPS from first boot. nginx serves TLS 1.2 and 1.3 with a self-signed end-entity certificate created on your instance (it can identify this server and nothing else). The password, API tokens and audio only ever travel over HTTPS. The browser session cookie is marked
Secure,HttpOnlyandSameSite=Lax. - The host name comes from configuration, never from a request. The name Euterpe is served as is your own (see Step 7) or the instance's public DNS name from EC2 instance metadata (IMDSv2). Requests for any other host name get their connection closed.
- Client address from the connection. nginx replaces
X-Forwarded-ForandX-Real-IPwith the real peer address and dropsForwardedandX-Forwarded-Host. - Cross-site protection. A request from another web site that would change something (any
POST,PUTorDELETE) is refused unless it comes from this instance's own pages. The fewGETaddresses that change state or can make the server fetch something (album artwork and artist images, sign-out, the device-pairing QR code) answer a browser only for this instance's own pages, so another site cannot use your signed-in browser against them; API clients and the Euterpe mobile app that send their own token are unaffected. Upstream's sign-in redirect could be pointed at another web site (?return_to=//other.site); nginx passes the sign-in form to Euterpe without its query string, so you always land on the library. - No secrets in the logs. Subsonic mobile players send the password in the address and the REST API accepts
?token=; nginx logs requests without their query string, logs nothing for requests to an unknown host name, and records errors only at the critical level. - The server cannot read instance metadata. Euterpe, its first-boot unit and nginx are blocked from the instance metadata service (169.254.169.254, fd00:ec2::254) by systemd
IPAddressDeny; only the small unit that looks up the instance's name at boot may use it, and it serves nothing. - Sandboxed. Euterpe runs as the unprivileged
euterpeuser with no capabilities, a read-only system (it may write only its database and log directories), no core dumps, and a kernel-level rule that lets it bind only TCP 9996 on loopback. - Dependencies. Upstream's 1.6.3 dependency graph reaches eight advisories in golang.org/x/image (image decoders that panic or allocate without bound on crafted artwork: GO-2026-6222, -5066, -5062, -5061, -5032, -5031, -4961, -4815) and ships golang.org/x/text 0.31.0 and SQLite 3.49.1; this build raises the three modules and
govulncheckreports no vulnerabilities in the shipped binary. The binary is built twice, in different directories, and must come out byte-identical; the record is in/usr/share/doc/euterpe/BUILD-PROVENANCE.txt. Upstream's own release downloads are compressed with UPX; this binary is a plain executable that security scanners can read. - The web player's bundled libraries. Euterpe's web interface bundles older versions of jQuery (1.10.2), Bootstrap (3.3.6) and URI.js (1.18.7), which have published cross-site-scripting advisories of medium severity. They only display data from your own music tags, and nginx sends a
Content-Security-Policythat allows scripts from this instance only, so injected script cannot run. They are part of Euterpe's upstream source and are not modified. - Hardened SSH and system. SSH allows keys only, no root login and no X11 forwarding. The AMI requires IMDSv2. SSH host keys and the machine ID are generated fresh on every instance.
Prerequisites
- An AWS account and permission to launch EC2 instances
- An EC2 key pair in the Region you launch in
- Your own music files (MP3, FLAC, Ogg Vorbis, Opus, M4A, WAV and similar)
Recommended instance type: m5.large. Euterpe streams files as they are, without transcoding, so a small instance serves many listeners; size the root volume (or attach a data volume, see Step 5) for your library.
Connecting to your instance
| Version | Operating system | SSH user |
|---|---|---|
| Euterpe 1.6.3 on Ubuntu 24.04 | Ubuntu 24.04 LTS | ubuntu |
Step 1: Launch the instance
Option A: AWS Marketplace console
- Open the cloudimg Hardened SRS Streaming Media Server & Media Servers AMIs listing on AWS Marketplace and choose View purchase options, then Subscribe. One subscription covers every version in the listing.
- Once the subscription is active, choose Launch your software.
- Choose One-click launch from AWS Marketplace (or Launch from EC2 Console for the full launch wizard). In Version, choose Euterpe 1.6.3 on Ubuntu 24.04, then choose your Region.
- Pick an instance type (
m5.largerecommended; the software charge is $0.04 per vCPU per hour) and your key pair. - Security group: the listing's default group opens SSH (22/tcp), HTTP (80/tcp) and HTTPS (443/tcp). Restrict SSH to your own IP address, and 443 to the networks you listen from if you can.
- Choose your VPC and a subnet, leave the metadata defaults (the image already requires IMDSv2), and add storage for your music if your library is large. Then choose Launch.
Option B: AWS CLI
Run this on your own workstation. Replace the AMI ID with the one shown for your Region on the Marketplace launch page, and the VPC, subnet, key pair and CIDRs with your own:
SG=$(aws ec2 create-security-group --group-name euterpe \
--description "Euterpe music server" --vpc-id vpc-0123456789abcdef0 \
--query GroupId --output text)
aws ec2 authorize-security-group-ingress --group-id "$SG" --protocol tcp --port 22 --cidr <your-mgmt-cidr>
aws ec2 authorize-security-group-ingress --group-id "$SG" --protocol tcp --port 80 --cidr <your-cidr>
aws ec2 authorize-security-group-ingress --group-id "$SG" --protocol tcp --port 443 --cidr <your-cidr>
aws ec2 run-instances \
--image-id ami-0123456789abcdef0 \
--instance-type m5.large \
--key-name my-key-pair \
--security-group-ids "$SG" \
--subnet-id subnet-0123456789abcdef0 \
--associate-public-ip-address \
--block-device-mappings 'DeviceName=/dev/sda1,Ebs={VolumeSize=50,VolumeType=gp3}' \
--tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=euterpe}]'
You do not need --metadata-options: the AMI already makes IMDSv2 required.
Step 2: Connect and find your password
Connect as ubuntu with the key pair you chose:
ssh -i ~/.ssh/my-key-pair.pem ubuntu@<public-ip>
First boot takes a few seconds after the instance starts. It reads the instance's public name, creates the TLS certificate, the admin password and the token-signing secret, and writes /root/euterpe-credentials.txt (mode 0600, root). These are the entries in that file (the password is not shown here):
sudo grep -E '^(EUTERPE_URL|EUTERPE_ADMIN_USERNAME|EUTERPE_LIBRARY_PATH)=' /root/euterpe-credentials.txt
sudo grep -oE '^[A-Z0-9_]+=' /root/euterpe-credentials.txt
sudo stat -c '%a %U:%G %n' /root/euterpe-credentials.txt /etc/euterpe/config.json
EUTERPE_URL=https://ec2-3-92-200-202.compute-1.amazonaws.com/
EUTERPE_ADMIN_USERNAME=admin
EUTERPE_LIBRARY_PATH=/var/lib/euterpe/music
EUTERPE_URL=
EUTERPE_ADMIN_USERNAME=
EUTERPE_ADMIN_PASSWORD=
EUTERPE_CERT_SHA256=
EUTERPE_LIBRARY_PATH=
600 root:root /root/euterpe-credentials.txt
640 root:euterpe /etc/euterpe/config.json
| Value | What it is for |
|---|---|
EUTERPE_URL |
The address of this instance's Euterpe |
EUTERPE_ADMIN_USERNAME / EUTERPE_ADMIN_PASSWORD |
The sign-in for the web player, the REST API and the Subsonic API (keep the password secret) |
EUTERPE_CERT_SHA256 |
The fingerprint of this instance's certificate, to check the browser warning against |
EUTERPE_LIBRARY_PATH |
Where your music goes (it ships empty) |
To read the password, run sudo cat /root/euterpe-credentials.txt in your own session and store it in your password manager.
Step 3: Confirm Euterpe is running
The network-configuration unit has run, first boot has finished, Euterpe and nginx are active, Euterpe listens on 127.0.0.1:9996 only, and only sshd and nginx listen on TCP beyond loopback:
systemctl is-active euterpe-netconf.service euterpe.service nginx.service
ls /var/lib/cloudimg/euterpe-firstboot.done
sudo ss -Htlnp | awk '{print $4, $6}' | sed -E 's/,pid=[0-9]+,fd=[0-9]+//g' | sort -u
euterpe -v
sudo sed -n '/^tag commit/p;/^sqlite/p;/^binary sha256/p;/^govulncheck/p' /usr/share/doc/euterpe/BUILD-PROVENANCE.txt
active
active
active
/var/lib/cloudimg/euterpe-firstboot.done
0.0.0.0:22 users:(("sshd"),("systemd"))
0.0.0.0:443 users:(("nginx"),("nginx"),("nginx"))
0.0.0.0:80 users:(("nginx"),("nginx"),("nginx"))
127.0.0.1:9996 users:(("euterpe"))
127.0.0.53%lo:53 users:(("systemd-resolve"))
127.0.0.54:53 users:(("systemd-resolve"))
[::]:22 users:(("sshd"),("systemd"))
[::]:443 users:(("nginx"),("nginx"),("nginx"))
[::]:80 users:(("nginx"),("nginx"),("nginx"))
Euterpe Media Server v1.6.3
Build with go1.27.3
tag commit 1a3bf30733ef2a6776ad2f15e1d919f7157d0533
sqlite 3.53.4 (bundled by github.com/mattn/go-sqlite3 v1.14.52, ICU extension on)
binary sha256 fce1b47ba8defe336a3989d649b58a318c4fe9584de41a9d3c3b9c58581875fb (two builds in different directories with separate caches: identical)
govulncheck v1.9.0 -mode=binary: No vulnerabilities found.
Step 4: Prove the defaults
Signed out, the web player sends you to the sign-in page, the REST API and the Subsonic API refuse a request without credentials or with a guessed password, plain HTTP redirects to HTTPS and an unknown Host header gets no answer. The last command is the image's own end-to-end check, which signs in with the instance's own password:
H=$(sed -n 's/^set \$euterpe_host "\(.*\)";$/\1/p' /etc/nginx/euterpe-host.conf)
C=(--connect-to ::127.0.0.1: --cacert /etc/nginx/tls/euterpe.crt)
curl -s "${C[@]}" -o /dev/null -w 'web player, signed out -> %{http_code} %{redirect_url}\n' -H 'Accept: text/html' "https://$H/"
curl -s "${C[@]}" -o /dev/null -w 'REST API, no credentials -> %{http_code}\n' -H 'Accept: application/json' "https://$H/v1/about"
curl -s "${C[@]}" -o /dev/null -w 'REST API, admin:admin -> %{http_code}\n' -u admin:admin "https://$H/v1/about"
curl -s "${C[@]}" "https://$H/rest/getArtists.view?v=1.16.1&c=guide&f=json" | jq -r '"Subsonic API, no credentials -> " + .["subsonic-response"].status + " (" + .["subsonic-response"].error.message + ")"'
curl -s --connect-to ::127.0.0.1: -o /dev/null -w 'http -> %{http_code} %{redirect_url}\n' "http://$H/"
curl -sk -o /dev/null -w 'unknown Host -> %{http_code}\n' -H 'Host: evil.example' https://127.0.0.1/ || true
sudo euterpe-selftest | tail -4
web player, signed out -> 302 https://ec2-3-92-200-202.compute-1.amazonaws.com/login/?return_to=%2F
REST API, no credentials -> 401
REST API, admin:admin -> 401
Subsonic API, no credentials -> failed (Required parameter is missing)
http -> 301 https://ec2-3-92-200-202.compute-1.amazonaws.com/
unknown Host -> 000
selftest: ok Euterpe TCP listener 127.0.0.1:9996 only
selftest: ok euterpe-netconf.service ran this boot
selftest: 31 checks
selftest: EUTERPE-SELFTEST OK
--connect-to ::127.0.0.1: sends each request to this instance while keeping the real host name in the request, exactly as a browser would send it. 000 means the server closed the connection without an answer.
Step 5: Add your music
The library ships empty. Copy your own music into /var/lib/euterpe/music (any folder layout; Euterpe groups tracks by the artist and album in their tags) and Euterpe indexes it within seconds, watching the folder for later changes. From your workstation:
rsync -av --progress -e "ssh -i ~/.ssh/my-key-pair.pem" ~/Music/ ubuntu@<public-ip>:/tmp/incoming/
ssh -i ~/.ssh/my-key-pair.pem ubuntu@<public-ip> \
'sudo rsync -a /tmp/incoming/ /var/lib/euterpe/music/ && sudo rm -rf /tmp/incoming'
Cover art comes from an image file in each album folder (any .jpg, .png or .gif; cover.* and front.* are preferred), or you can upload one through the REST API with an API token (PUT /v1/album/<id>/artwork, see below). For a large library, attach an EBS volume, mount it (for example at /srv/music) and add it to the libraries list in /etc/euterpe/config.json, then restart Euterpe:
sudo nano /etc/euterpe/config.json # "libraries": ["/var/lib/euterpe/music", "/srv/music"]
sudo systemctl restart euterpe
Euterpe only needs to read your music; files owned by root with the usual permissions (0644, folders 0755) are fine.
Step 6: Sign in and listen
Browse to EUTERPE_URL (https://<public-dns-name>/). The certificate is self-signed, so your browser warns once: compare the fingerprint it shows with EUTERPE_CERT_SHA256 and continue only if they match, or serve Euterpe under your own name with a certificate from a public authority (Step 7). Sign in as admin with EUTERPE_ADMIN_PASSWORD; tick Remember me to stay signed in on this browser.

The player opens with an empty play queue. Type in the search box to find artists, albums and tracks, or choose Load All Music to queue the whole library; the Artist and Album lists then filter the queue.

Choose an album to narrow the queue to it, then the play button: the album's cover art appears and playback continues through the queue.

Mobile apps and the APIs
- Subsonic apps (for example DSub, Ultrasonic, substreamer, play:Sub, Symfonium): add a server with the address
EUTERPE_URL, the user nameadminandEUTERPE_ADMIN_PASSWORD. The app must accept this instance's certificate, or use your own name and a trusted certificate (Step 7). - The Euterpe mobile app pairs with a QR code: choose Add Device in the web player's top bar and, in the app, choose Use Barcode when signing in and scan the code. It contains an API token for this server (valid for six months); treat the screen like a password.

- REST API (documented upstream in
/usr/share/doc/euterpe/API.md): exchange the user name and password for a token atPOST /v1/login/token/and send it asAuthorization: Bearer <token>. Tokens are signed with the instance's own secret, so they work only on this instance.
Step 7: Serve Euterpe under your own DNS name
Point a DNS record (for example music.example.com) at the instance's public or Elastic IP address, then tell the image to serve that name. It creates a new self-signed certificate for the name, pins nginx and its cross-site protection to it, updates the address in the credentials file and keeps the name across reboots:
sudo euterpe-set-hostname music.example.com
For a certificate that browsers and apps accept without a warning, obtain one for that name (for example with certbot's certonly mode using its own temporary web server, or from your organisation's CA), copy the certificate chain and key over /etc/nginx/tls/euterpe.crt and /etc/nginx/tls/euterpe.key (key mode 0600, root), and run sudo systemctl reload nginx.
Step 8: Passwords, tokens and the security controls
- New password:
sudo euterpe-rotate-passwordwrites a new admin password and a new token-signing secret, so every API token, paired app and browser session stops working at once; sign in again and re-pair your apps. The guard refuses passwords shorter than 24 characters. - Remote artwork lookups are off. Euterpe can look missing album and artist pictures up on MusicBrainz, the Cover Art Archive and Discogs (
"download_artwork": truein/etc/euterpe/config.json, plus"discogs_auth_token"for artist images). It then contacts those services on its own; the metadata block above still applies.
The security controls behind these defaults can be checked at any time:
systemctl show -p IPAddressDeny euterpe.service euterpe-firstboot.service nginx.service
systemctl show -p User -p CapabilityBoundingSet -p SocketBindAllow -p SocketBindDeny euterpe.service
sudo stat -c '%a %U:%G %n' /var/lib/euterpe-home /var/lib/euterpe-home/.euterpe
sudo jq '{listen, basic_authenticate, download_artwork, libraries}' /etc/euterpe/config.json
openssl x509 -in /etc/nginx/tls/euterpe.crt -noout -ext basicConstraints,keyUsage,extendedKeyUsage
sudo sshd -T | grep -E '^(permitrootlogin|passwordauthentication|x11forwarding) '
IPAddressDeny=169.254.169.254/32 fd00:ec2::254/128 168.63.129.16/32
IPAddressDeny=169.254.169.254/32 fd00:ec2::254/128 168.63.129.16/32
IPAddressDeny=169.254.169.254/32 fd00:ec2::254/128 168.63.129.16/32
SocketBindAllow=tcp9996
SocketBindDeny=any
CapabilityBoundingSet=
User=euterpe
555 root:root /var/lib/euterpe-home
555 root:root /var/lib/euterpe-home/.euterpe
{
"listen": "127.0.0.1:9996",
"basic_authenticate": true,
"download_artwork": false,
"libraries": [
"/var/lib/euterpe/music"
]
}
X509v3 Basic Constraints: critical
CA:FALSE
X509v3 Key Usage: critical
Digital Signature, Key Encipherment
X509v3 Extended Key Usage:
TLS Web Server Authentication
permitrootlogin no
passwordauthentication no
x11forwarding no
Maintenance
- Operating system updates: unattended security upgrades are on. Reboot when
/var/run/reboot-requiredexists; Euterpe and nginx start again by themselves. - Euterpe updates: Euterpe is a single binary at
/usr/local/bin/euterpe. To move to a newer release, launch the newer version of this image when the listing offers it and copy your music,/etc/euterpe/config.jsonand (to keep playlists and play counts)/var/lib/euterpe/euterpe.dbacross. - Backups: your music, the configuration and the library database
/var/lib/euterpe/euterpe.db(playlists, uploaded artwork, statistics). Euterpe rebuilds the index from the music files if the database is lost. - Logs:
/var/log/euterpe/euterpe.log(rotated weekly) andsudo journalctl -u euterpefor Euterpe,/var/log/nginx/for web requests (no query strings). - What the instance contacts: Euterpe itself contacts nothing unless you switch on remote artwork lookups (Step 8). Measured on a new instance for ten minutes from just after its first boot, every outbound connection came from the operating system: the instance metadata service, the AWS Systems Manager agent (
ssm,ec2messagesand its S3 bucket) and the time service (Amazon Time Sync plus Ubuntu's NTP pool). Euterpe and nginx made none. Disable the SSM agent if you do not use Session Manager.
Troubleshooting
- The library stays empty: check that your files are under a path in
librariesand readable (sudo -u euterpe ls /var/lib/euterpe/music), and look at/var/log/euterpe/euterpe.logfor the scan. - Euterpe does not start after you edited the configuration:
sudo journalctl -u euterpe -n 20shows the guard's reason. It refuses a configuration without"basic_authenticate": true, without"listen": "127.0.0.1:9996", or with a password shorter than 24 characters or a signing secret shorter than 64. - A mobile app cannot connect: most apps do not accept a self-signed certificate. Use your own DNS name and a trusted certificate (Step 7).
- Browser shows "connection closed": nginx answers only the configured name and the instance's own addresses. Use
EUTERPE_URL, or runsudo euterpe-set-hostname <name>for a new name. - The end-to-end check:
sudo euterpe-selftestchecks HTTPS, every sign-in path and the listening ports, and printsEUTERPE-SELFTEST OKwhen everything works.
Licensing
Euterpe is licensed under the GNU General Public License version 3. The licence text, a written offer for the corresponding source, the licence of every library compiled into the binary and the build record are on the instance in /usr/share/doc/euterpe/; the source of this release is at github.com/ironsmile/euterpe/tree/v1.6.3. You are responsible for having the rights to the music you store and stream. cloudimg is not affiliated with, endorsed by or sponsored by the Euterpe project.
Support
cloudimg provides 24/7 technical support for this image by email and live chat: deployment, first sign-in, HTTPS and DNS setup, storage for your library and troubleshooting. Support for Euterpe itself comes from its open-source community. Contact support@cloudimg.co.uk.