I've been messing with self-hosted web radio lately — AzuraCast is the obvious pick if you want Icecast streaming, Liquidsoap playlist automation, a browser-based DJ console, podcasts and listener stats without paying for a hosted service. It's been around for about ten years and it's genuinely good software.
If you're setting it up on any platform where the app comes up publicly before you've had a chance to click through the wizard yourself (Railway, Render, a VPS with a slow DNS propagation, whatever), there's a footgun worth knowing about before you hit deploy.
The setup wizard doesn't check who's asking.
SetupController::registerAction serves GET /setup/register and accepts the POST with no authentication at all, for as long as the users table is empty. Whoever's account gets created there gets ensureSuperAdministratorRole() — full admin, no questions asked. I checked this directly against the stock image:
GET /setup/register -> 200 (valid CSRF token in the page)
POST /setup/register -> 302
azuracast_cli azuracast:account:list
attacker@example.com Super Administrator
That's not a hypothetical bot-scanning-the-internet scenario either — it's the literal next step in the standard deploy instructions most guides give you: "open your URL and finish the setup wizard." If your app is reachable before you are, whoever gets there first owns your instance — your stream config, your DJ accounts, everything.
The fix, if you're doing this yourself: don't expose the public URL until you've completed the wizard, or front it with something (Cloudflare Access, a temporary firewall rule, whatever you've got) until the first admin account exists. AzuraCast doesn't help you here — this is on the deployer.
Full disclosure: I maintain a Railway template for AzuraCast (kickback applies if you deploy through it) that seeds the admin account server-side before nginx ever starts serving requests, so there's no window where the setup page is live and unclaimed — verified live, /setup/register returns 403 from the first request the domain ever answers. It also pins the version instead of tracking :latest (AzuraCast's DB migrations are one-way, so an unplanned upgrade on redeploy has no way back), and sizes PHP-FPM/MariaDB pool limits from the container's actual memory instead of AzuraCast's fixed defaults, which otherwise assume way more RAM than a small box has.
Link if you want the one-click version: https://railway.com/deploy/azuracast-v0237-or-web-radio-whose-admin?referralCode=Z1xivh&utm_medium=integration&utm_source=template&utm_campaign=inventory
And if you'd rather run it yourself with none of that: upstream repo is https://github.com/AzuraCast/AzuraCast, official Docker install docs are on their site — just make sure you're not leaving /setup/register open to the internet even for five minutes.
Top comments (0)