ICECAST WITH HTTPS

1. Install and configure Icecast listenning on 127.0.0.1 port 8000 without ssl. ;)
2. Install nginx

3. Create directory .well-known in webroot ( here: /var/www/html )
4. Install certbot
5. Configure nginx:
/etc/nginx/nginx.conf:

#user www;
worker_processes 4;
#pid /run/nginx.pid;

events {
        worker_connections 768;
        # multi_accept on;
}

http {
        ##
        # Basic Settings
        ##

        sendfile on;
        tcp_nopush on;
        tcp_nodelay on;
        keepalive_timeout 65;
        types_hash_max_size 2048;
        # server_tokens off;

        # server_names_hash_bucket_size 64;
        # server_name_in_redirect off;

        include /etc/nginx/mime.types;
        default_type application/octet-stream;

        ##
        # SSL Settings
        ##

        ssl_protocols TLSv1 TLSv1.1 TLSv1.2; # Dropping SSLv3, ref: POODLE
        ssl_prefer_server_ciphers on;

        ##
        # Logging Settings
        ##

        #log_format icecast_combined '$remote_addr - $remote_user [$time_local] '"$request" $status $body_bytes_sent ' '"$http_referer" "$http_user_agent" ' '$request_time $upstream_response_time $pipe';
         log_format icecast_combined '$remote_addr - $remote_user [$time_local] '"$request" $status $body_bytes_sent ' '"$http_referer" "$http_user_agent" ' '$upstream_response_time';
        access_log /var/log/nginx/access.log;
        error_log /var/log/nginx/error.log debug;

        ##
        # Gzip Settings
        ##

        gzip on;
        gzip_disable "msie6";

        # gzip_vary on;
        # gzip_proxied any;
        # gzip_comp_level 6;
        # gzip_buffers 16 8k;
        # gzip_http_version 1.1;
        # gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;

        ##
        # Virtual Host Configs
        ##

        #include /etc/nginx/conf.d/*.conf;
        include /etc/nginx/sites-enabled/*;
}

/etc/nginx/sites-available/icecast:

server
{
  listen 80;
  server_name icecast.mydomain.com;

  location ~ /.well-known
  {
    allow all;
  }

  location /
  {
    if ($ssl_protocol = "")
    {
      rewrite ^ https://$server_name$request_uri? permanent;
    }
  }
}

#### SSL ######################################################

server
{
  #ssl on;
  ssl_certificate_key /etc/letsencrypt/live/icecast.mydomain.com/privkey.pem;
  ssl_certificate /etc/letsencrypt/live/icecast.mydomain.com/fullchain.pem;
  ssl_dhparam /etc/ssl/certs/dhparam.pem;
  # Recommended security settings from https://wiki.mozilla.org/Security  /Server_Side_TLS
  ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
  ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA';
  ssl_prefer_server_ciphers on;
  ssl_ecdh_curve secp384r1;
  ssl_session_timeout 5m;
  ssl_session_cache shared:SSL:5m;
  ssl_session_tickets off;
  ssl_stapling on;
  ssl_stapling_verify on;
  resolver 8.8.8.8 8.8.4.4 valid=300s;
  resolver_timeout 5s;
# Enable this if you want HSTS (recommended)
# With or without preload (without very secure but not recommended)
  add_header Strict-Transport-Security "max-age=15768000; includeSubdomains;";
#add_header Strict-Transport-Security "max-age=15768000; includeSubdomains; preload;";
  add_header X-Frame-Options DENY;
  add_header X-Content-Type-Options nosniff;
  listen 443 ssl;
  root /var/www/html;

  server_name icecast.mydomain.com;

  location ~ /.well-known
  {
    allow all;
  }

  location /
  {
    access_log /var/log/icecast/access_https.log icecast_combined;
    proxy_pass http://127.0.0.1:8000/;
    proxy_redirect off;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
  }
}

ATTENTION: https logging
Connection via https will be always logged as 127.0.0.1 by icecast.
That’s the reason for
log_format icecast_combined … in nginx.conf
access_log /var/log/icecast/access_https.log icecast_combined; in /etc/nginx/sites-available/icecast

6. Create DH Group

openssl dhparam -out /etc/ssl/certs/dhparam.pem 2048

7. Get Certificates

certbot certonly -a webroot --webroot-path=/var/www/html -d icecast.mydomain.com

8. Configure cert renewing

 @monthly /usr/bin/certbot renew >> /var/log/le-renew.log; /usr/bin/pkill -HUP nginx

9. Tune the m3u link to the correct mount point/port.
(Credits: https://deephouse.lv – Thank you! // Check https://deephouse.lv/audio/02_2018.mp3 )

10. RUN Icecast

 icecast -b -c /etc/icecast2/icecast.xml

11. Run nginx

nginx

More hints (HUGE THX, Michael!):

Although this configuration may work it would benefit from several changes.

In your icecast virtual hosts file you have specified two separate server blocks for icecast.mydomain.com, which adds unnecessary overhead to each request. You can combine them into a single block or if your goal is actually to redirect all http traffic to https then you can specify a catch all server block for any traffic on port 80 like this:

server {
listen 80 default_server;
return 301 https://$host$request_uri;
}

This improves on your “location /” block in a couple of ways. It eliminates the evaulation of an if condition (which you should also avoid specifying within location blocks, but that’s a different topic) and by using return instead of rewite the regular expression does not need to be processed either. If you prefer to merge the two server blocks into one you can specify an if directive within the server block but before any location blocks like this to redirect all http traffic to https:

if ($scheme != “https”) {
return 301 https://icecast.mydomain.com$request_uri;
}

The regex specified here “location ~ /.well-known” is incorrect and potentially creates a security risk. The period before the w is unescaped (should be “\.”). An unescaped period in regex means match any character and you have not specified a position in the url for this expression to appear, it will match anywhere. If you had a password protected section under the location “/private” then I could visit icecast.mydomain.com/private/pwell-known and the “allow all” directive from this location would be the match made by Nginx. To allow access to anything within the dir “/.well-known” your location block should be like this:

location ^~ /.well-known {
allow all;
}

In regex the caret specifies the beginning of the string, but in Nginx this actually means something different. Here it is not specifyingn a regex match but a prefix, and the caret tells Nginx to stop evaluating any further possible matches after this one has matched.

Finally, and most usefully, instead of having a single location block proxying the request directly to the icecast server you should create another upstream server within Nginx to receive the proxied request and relay it to the icecast server. Within it’s server block include:

set_real_ip_from 127.0.0.1;
real_ip_header X-Real-IP;
real_ip_recursive on;

and your client IP addresses should be passed to Icecast properly for the stats/logging of listener IPs instead of every connection appearing to originate from 127.0.0.1.

 

15 thoughts on “ICECAST WITH HTTPS

  1. Hello, i have this error when i start nginx :

    Dec 16 11:25:06 sd-118823 nginx[17467]: nginx: [emerg] unexpected “”” in /etc/nginx/nginx.conf:40

    There is a problem at this line : log_format icecast_combined ‘$remote_addr – $remote_user [$time_local] ‘”$request” $status $body_bytes_sent ‘ ‘”$http_referer” “$http_user_agent” ‘ ‘$upstream_response_time’;

    Can you help me please ? thanks a lot

  2. Hi! Thanks for this!
    My enginx complains about line 40:
    `log_format icecast_combined ‘$remote_addr – $remote_user [$time_local] ‘”$request” $status $body_bytes_sent ‘ ‘”$http_referer” “$http_user_agent” ‘ ‘$upstream_response_time’;`

    It says :
    `nginx: [emerg] unexpected “”” in /etc/nginx/nginx.conf:40`

    I can’t identify which one is faulty. Would you be so kind and help me?

  3. Sorry for my previous comments, i didn’t see that you had already answered another user with the same problem. feel free to delete those.

    Nginx and icecast2 both works fine with this config, nginx will serve on port 80, iceacast2 is happy on port 8000 but there is no redirection to https happeing when i visit the url in http. If i type the url with https, no connection.

    Any hint or links to further documentation that may help?

    1. So the step i was missing is to include /etc/nginx/sites-available/icecast in /etc/nginx/sites-enabled/

      I try to do a symbolic link to it as is done with /etc/nginc/sites-enabled/default

      But then nginx complains with this:
      nginx: [emerg] invalid number of arguments in “add_header” directive in /etc/nginx/sites-enabled/icecast:44

  4. gah…
    i had to fix the path to logs from
    /var/log/icecast/access_https.log
    to
    /var/log/icecast2/access_https.log

    sorry for the noise.

  5. Hello, very good tutorial, but I am under debian 9 Icecast 2.4.3 and Apache / 2.4.25 (Debian), and the problem is that the proposed configuration is for NGINX. The translation for Apache is possible? Need urgent help on the subject! Contact me by email if you can. Thanks and best regards, Patrick

  6. Although this configuration may work it would benefit from several changes.

    In your icecast virtual hosts file you have specified two separate server blocks for icecast.mydomain.com, which adds unnecessary overhead to each request. You can combine them into a single block or if your goal is actually to redirect all http traffic to https then you can specify a catch all server block for any traffic on port 80 like this:

    server {
    listen 80 default_server;
    return 301 https://$host$request_uri;
    }

    This improves on your “location /” block in a couple of ways. It eliminates the evaulation of an if condition (which you should also avoid specifying within location blocks, but that’s a different topic) and by using return instead of rewite the regular expression does not need to be processed either. If you prefer to merge the two server blocks into one you can specify an if directive within the server block but before any location blocks like this to redirect all http traffic to https:

    if ($scheme != “https”) {
    return 301 https://icecast.mydomain.com$request_uri;
    }

    The regex specified here “location ~ /.well-known” is incorrect and potentially creates a security risk. The period before the w is unescaped (should be “\.”). An unescaped period in regex means match any character and you have not specified a position in the url for this expression to appear, it will match anywhere. If you had a password protected section under the location “/private” then I could visit icecast.mydomain.com/private/pwell-known and the “allow all” directive from this location would be the match made by Nginx. To allow access to anything within the dir “/.well-known” your location block should be like this:

    location ^~ /.well-known {
    allow all;
    }

    In regex the caret specifies the beginning of the string, but in Nginx this actually means something different. Here it is not specifyingn a regex match but a prefix, and the caret tells Nginx to stop evaluating any further possible matches after this one has matched.

    Finally, and most usefully, instead of having a single location block proxying the request directly to the icecast server you should create another upstream server within Nginx to receive the proxied request and relay it to the icecast server. Within it’s server block include:

    set_real_ip_from 127.0.0.1;
    real_ip_header X-Real-IP;
    real_ip_recursive on;

    and your client IP addresses should be passed to Icecast properly for the stats/logging of listener IPs instead of every connection appearing to originate from 127.0.0.1.

Leave a reply to # Cancel reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.