Background
This is a guide for setting up basic radio stations (with SSL and port 80) using Shoutcast and Liquidsoap. There’s no GUI and my stations only shuffle music, I don’t do live sets. It is definitely possible, but I won’t cover it here.
VPS Requirements
You’ll most likely need a VPS with unlimited bandwidth. Also, if you plan to broadcast the same stream using multiple bitrates or codecs, you’ll need a decent CPU because Liquidsoap only uses one CPU core per instance. (There may be some ways around this limitation, but I am currently doing fine running 3 different stations, each at 2 different bit rates. I’m using a 4-core VPS at Scaleway (DEV-L), so each station is using 1 core for its Liquidsoap instance, and the CPU load on each core is around 50%.)
System Prep
Choose a VPS provider and spin up a new VPS running Debian (or Ubuntu).
Once you have the IP for your VPS, you can create an A record in your domain’s DNS to point to the VPS. In this guide I’ll use radio.example.com.
SSH into the VPS and check for updates.
apt update && apt -y upgrade
Create a user, such as “radio” and add to the sudo group for root privileges:
adduser radio adduser radio sudo
Exit and log back in as your new user (or switch to the new user: su - radio
).
Install Shoutcast
Additional info can be found at wiki.winamp.com/wiki/SHOUTcast_Broadcaster.
mkdir ~/sc && cd ~/sc curl -O https://download.nullsoft.com/shoutcast/tools/sc_serv2_linux_x64-latest.tar.gz tar -xvf sc_serv2_linux_x64-latest.tar.gz rm -f sc_serv2_linux_x64-latest.tar.gz
Setup Shoutcast
You could use the setup script:
./sc_serv setup
Then point your browser to the IP of you VPS plus the port, e.g. http://111.111.111.111:8000/setup
Or just copy the basic config example and start editing it.
cp examples/sc_serv_basic.conf shoutcast.conf nano shoutcast.conf
Here is my config file:
logfile=logs/sc_serv.log w3clog=logs/sc_w3c.log banfile=control/sc_serv.ban ripfile=control/sc_serv.rip maxuser=2000 songhistory=50 portbase=80 ;used by stream sources password=yourPassword ;used to the admin web interface adminpassword=yourPassword streamid_1=1 streampath_1=/station1-128 streamauthhash_1=yourAuthHashFromRmo.shoutcast.com streamid_2=2 streampath_2=/station1-256 streamauthhash_2=yourAuthHashFromRmo.shoutcast.com streamid_3=3 streampath_3=/station2-128 streamauthhash_3=secondAuthHashFromRmo.shoutcast.com streamid_4=4 streampath_4=/station2-256 streamauthhash_4=secondAuthHashFromRmo.shoutcast.com
I am using port 80 because of some listeners behind firewalls. Because 80 is special, I have to start Shoutcast as root. We’re logged in with the radio user, so use sudo:
sudo ~/sc/sc_serv daemon ~/sc/shoutcast.conf
The daemon option will keep sc_serv running in the background.
Get some Music
mkdir ~/music cd ~/music
Begin uploading music with an SFTP client, such as ForkLift or Cyberduck.
If you happen to be moving from an existing radio host, and all your music is already on their server, you can transfer directly using ncftp
.
(If you have SSH access to the old server, it’s much faster to use rsync, it will be something like rsync -azv user@old.ser.ver.ip:~/music ~/music
)
sudo apt install ncftp ncftpget -RTvu 'username' server.oldhost.com ~/music/ /media
(Replace /media with the path to your music folder on the old host. /media is the normal folder for CentovaCast setups. ~/music/ is where you’re moving the music TO on your VPS.)
This will probably take a long time, and you don’t want to ncftp process to stop if your connection drops, so it is best to use screen
for this. Screen
will allow it to continue in the background.
Simply enter screen
and you’ll get a new prompt. Start the ncftp command, and then leave the screen instance with control + a then d (for detach). You can return to the screen by typing screen -r
Install Liquidsoap
Liquidsoap recommends the OPAM installation method, but there are other options.
Be sure to login as the radio user for the Liquidsoap install. When using OPAM to install Liquidsoap, it will only run for the user who installed it.
I like installing the latest version with the official install script, but you can just install an older version via apt install opam
. More info here.
If using the script to install, you’ll probably need to install some dependencies. You could wait until you run the opam init command and see what it tells you, or install these now, which are ones I’ve had to install on fresh systems. (Use dnf or apt depending on the OS, you should know by now):
sudo apt install unzip bubblewrap patch build-essential
And install:
sh <(curl -sL https://raw.githubusercontent.com/ocaml/opam/master/shell/install.sh)
When using OPAM for the first time, you should run
opam init
and answer y (for yes) to the question it asks about whether you want OPAM to modify some configuration files (this will put the directory where Liquidsoap in your path).
Install dependencies* and then liquidsoap:
opam install depext opam depext taglib mad lame vorbis flac cry samplerate ocurl liquidsoap opam install taglib mad lame vorbis flac cry samplerate ocurl liquidsoap
* First 2 lines no longer needed from opam 2.1
(You may need to run eval $(opam env --switch=default)
for the opam
command to work this first time.)
Setup Liquidsoap
Liquidsoap is extremely flexible and there’s a lot to learn if you want to dig into the docs. I’m just going to walk through my config file.
Liquidsoap configs end in .liq, so let’s make one for the first station:
mkdir -p ~/liquidsoap/logs nano ~/liquidsoap/station1.liq
And here’s my config:
# run in the background set("init.daemon",true) # set the location for the pid file which the daemon creates set("init.daemon.pidfile.path","~/liquidsoap/<script>.pid") # set a log file location # (<script> will be filled in automatically with the name of this file) set("log.file.path","~/liquidsoap/logs/<script>.log") # This creates the main playlist by pulling all media in the path (including subfolders) # the "watch" mode refreshes the playlist any time a new file is added # song order is randomized by default standard = playlist(reload_mode="watch","/home/radio/music/station1") radio = standard # add crossfade radio = crossfade(duration=5.0,fade_out=3.0,fade_in=3.0,radio) # make the source "infallible" by adding silence if the source does fail radio = mksafe(radio) # Send Meta to TuneIn AIR API # also remove this line if not using radio = on_metadata(send_meta, radio) # Stream it out # Mostly self-explanitory values # the password is what we set in shoutcast.conf (the "password" not the "adminpassword") # the icy_id corresponds to the "stream_id" in shoutcast.conf output.shoutcast(%mp3(bitrate=128), port = 80, password = 'xxxxxxxxxxxxxx', name = "Ambient Sleeping Pill", url = "https://ambientsleepingpill.com", genre = "Ambient", icy_id = 1, radio) output.shoutcast(%mp3(bitrate=256), port = 80, password = 'xxxxxxxxxxxxxx', name = "Ambient Sleeping Pill", url = "https://ambientsleepingpill.com", genre = "Ambient", icy_id = 2, radio)
Once you save the .liq file, you can start it up:
liquidsoap ~/liquidsoap/station1.liq
At this point the station should be up and running. If you visit your domain or IP in a browser, you should see the shoutcast summary page with the connected source, metadata, and a play button! If not, check on both the liquidsoap logs and the shoutcast logs for some clues.
SSL
You may want your stream to have https compatibility, perhaps to have the stream on your site without a SSL mixed content warning.
You’ll need an SSL certificate. You can issue a free certificate from Let’s Encrypt. I like to use acme.sh for issuing the certificates.
acme.sh
First, I logged in as root, because I thought it was recommended (maybe it doesn’t matter).
Install acme.sh
curl https://get.acme.sh | sh
As the install mentions, exit the terminal session and log back in to start uysing the acme.sh command.
Make a dir to hold the certificates, such as in the “radio” user’s home dir:
mkdir /home/radio/ssl/
I like to use the DNS method for domain verification (since you’re running shoutcast and don’t have a traditional webroot where you can place a verification file). I use Cloudflare’s free DNS for this server, and acme.sh can automatically add the DNS record for verification. They support many other DNS providers.
I’m using the “global API key” available from your Cloudflare profile.
Enter your info like so, and it will be saved to the config (~/.acme.sh/account.conf)
export CF_Key='xxxxxxxxxxxxxxxx' export CF_Email='your@email.com'
Then issue the cert, specifying the installation dir you created:
acme.sh --issue --server letsencrypt --dns dns_cf -d radio.example.com --fullchain-file /home/radio/ssl/fullchain.crt --key-file /home/radio/ssl/key.pem
(Notice the “–dns dns_cf” option. If you’re not using Cloudflare, remove “dns_cf” and the –dns flag will give you a manual DNS mode.)
Shoutcast Config
These are the settings related to SSL that you’ll need in the shoutcast config:
portbase=443 alternateports=80 sslCertificateFile=/home/radio/ssl/fullchain.crt sslCertificateKeyFile=/home/radio/ssl/key.pem publicip=radio.example.com destip=radio.example.com
In your Liquidsoap conf files, replace port = 80
with port = 443
.
Restart the server (run these from the radio user account not root)
sudo killall sc_serv && sudo killall liquidsoap
sudo ~/sc/sc_serv daemon ~/sc/shoutcast.conf
liquidsoap ~/liquidsoap/station1.liq
Go ahead and test it by visiting https://radio.example.com. You should see a nice green lock or whatever in the URL bar.
Multiple Stations
You can run additional stations by making additional .liq files and starting them up. If you are encoding each station to multiple bitrates and/or codecs, I’d recommend only one station per CPU core. Obviously CPUs vary, so be sure to monitor the system load and upgrade your VPS if needed.
You might want to make a little bash script to help you start everything up if you ever need to restart:
nano ~/start.sh
Fill in your config files:
sudo sc/sc_serv daemon sc/shoutcast.conf liquidsoap /liquidsoap/station1.liq liquidsoap /liquidsoap/station2.liq liquidsoap /liquidsoap/station3.liq
Make the script executable:
chmod u+x start.sh
Now you can run it like ./start.sh
after a system reboot and everything will be up and running.
ulimit
I don’t know if this is still needed, but it was at one point. I get over a thousand listeners at a time, and I had to raise the “max open files descriptor” on my system.
You can check it with ulimit -n
. Mine was 1024 and I doubled it via ulimit -n 2048
.
The limit is different for root and your radio user, and I don’t know which was relavent, so I logged in and changed it for both.
Also, the limit reset if I reboot the server, so I had to add it to /etc/security/limits.conf
like so:
root - nofile 2048 * - nofile 2048
Maintenance
There shouldn’t be much maintenance to do, but you should definitely keep an eye on the system load and memory usage for a while.
Install htop:
sudo apt install htop
and run it:
htop
Here you can keep an eye on the CPU usage (you will see meters at the top, one for each core) and the memory. If any of these is staying above 70% or so I would be worried that the system could crash.
The “Load average” shows 3 numbers. The first is an average for the past 1 minute, the second is the past 5 minutes, and the third is 15. The number corresponds to the number of CPU cores you have. I have 4 cores, so if the average was 4, I’d be at 100% usage and should be concerned. For reference, my 15-minute is always hovering around 1 so the system doesn’t have much stress running 3 stations with 2 bitrates each.
I like to use “Tree” view (F5 or just click on “Tree”). If you ever need to stop one of the processes just select it and click “Kill” (F9).
Exit htop with q.
Those are just some hints, but read up on htop on your own.
Reloading Config Changes
If you ever change the config for Shoutcast, you can reload the config from the admin web interface—no restart required.
Unfortunately, Liquidsoap needs to be restarted if you change the .liq configs. You can use htop to kill the correct Liquidsoap instance and then restart it.
I am setting up a new shoutcast server on Ubuntu 18.04, and the copy of sc_trans I have will not run in that environment (through it runs just fine on my existing Ubuntu 14.04 server. So since I can’t use the very simple-to-set up sc_trans (I have used it 24 x 7 for 4 years….) I now have to try and use the much more complicated liquidsoap as my source.
I have sc_serv running OK on my Ubuntu 18.04 box, and it appears that my liquidsoap install went ok
I used your config as a guide, but I am just using a text playlist so I changed that, and I’m not using the TuneIn stuff, so I commented that out.
HOWEVER when I start it up ~/liquidsoad/station1.liq I get the following error”
At line 31 char 18 – line 32 char 0: the variable standard used here has not been previously defined
I assume the error is referring to lines 31 & 32 in station1.liq, and NOT lines in my playlist.
Can you quickly look over this .liq configuration and help me figure out what’s wrong? I looked at lines 31…32 and don’t see what the error means by “variable standard”
run in the background
set(“init.daemon”,true)
# set the location for the pid file which the daemon creates
set(“init.daemon.pidfile.path”,”~/liquidsoap/.pid”)
# set a log file location
# ( will be filled in automatically with the name of this file)
set(“log.file.path”,”~/liquidsoap/logs/.log”)
# TuneIn AIR API function
# see http://tunein.com/broadcasters/api/
# This handles sending live metatdata updates to tunein.com, if you use that
# Replace with your partner id, partner key, and station id
#def send_meta(m) =
# partnerId = “?partnerId=xxxxxxx”
# partnerKey = “&partnerKey=xxxxxxx”
# id = “&id=s111111”
# api_server = “http://air.radiotime.com/Playing.ashx”
# title = “&title=” ^ url.encode(m[“title”])
# artist = “&artist=” ^ url.encode(m[“artist”])
# album = “&album=” ^ url.encode(m[“album”])
# uri =( api_server ^ partnerId ^ partnerKey ^ id ^ artist ^ title ^ album )
# ignore(http.get(uri))
#end
# end TuneIn code, remove if not using.
# Main playlist
music1 = playlist(“/home/noir/content/Playlists/noirlist1.txt”,mode=”sequential”)
radio = standard
# add crossfade
#radio = crossfade(start_next=10.0,fade_out=0.5,fade_in=0.5,radio)
# make the source “infallible” by adding silence if the source does fail
radio = mksafe(radio)
# Send Meta to TuneIn AIR API
# also remove this line if not using
#radio = on_metadata(send_meta, radio)
# Stream it out
# Mostly self-explanitory values
# the password is what we set in shoutcast.conf (the “password” not the “adminpassword”)
# the icy_id corresponds to the “stream_id” in shoutcast.conf
output.shoutcast(%mp3(bitrate=48),
host=”localhost”, port = 8000,
password = ‘redacted’,
name = “Audio Noir TEST”,
url = “https://AudioNoir.com”,
genre = “Old Time Radio”,
icy_id = 1,
radio)
You haven’t defined the variable “standard” that’s on the line “radio = standard”
It seems you’ve used “music1” instead, so change that to “radio = music1”
Hey Andrew, thanks for this tutorial. I have a question: since now Chrome doesn’t support http streams, I followed you guide on the SSL section… Can you confirm if I need to replace the port from 8000 to 443 for Liquisoap to successfully connect to Shoutcasr server?
output.shoutcast(%mp3(bitrate=48),
host=”localhost”,
port = 443, <— is this OK??
password = ‘redacted’,
name = “Audio Noir TEST”,
url = “https://AudioNoir.com”,
genre = “Old Time Radio”,
icy_id = 1,
radio)
If you’re using stunnel for the SSL, then no, don’t change the port. Set up the stunnel config to forward 443 to 8000
Hi Andrew,
in the SSL part, can radio.example.com be replaced by the IP address of the Shoutcast server instead of a domain name?
Firstly a huge thank you for this tutorial that helped me to make my own online dream radio at https://gothslothradio.gr
Secondly for anyone out there that needs this information
The crossfade code at the script above does not work and i could not find why.
I suspect it is because of the predefined script location from the opam installation, but i am not sure yet.
After a lot of experimentation i found that the code below works!!
Just change it from
# add crossfade
#radio = crossfade(start_next=10.0,fade_out=0.5,fade_in=0.5,radio)
To
# add crossfade
radio = crossfade(radio, smart=true)
You don’t have total control of the process as you cannot define the fade in and out but at least it works.
Thank you again Andrew, have a nice day everyone.
Thanks for pointing that out, I updated the article. That crossfade line must be from quite an old version of liquidsoap, oops!
Looking at my current config, I have:
radio = crossfade(duration=10.0,fade_out=0.5,fade_in=0.5,radio)
(fyi those are probably not great values for most music, but it’s for ambient music)