Enabling HTTPS for your Pi-hole Web Interface

lighttpd/1.4.45 (ssl) - a light and fast webserver
Build-Date: Jun 24 2019 22:58:56

using the default lighttpd.conf and the suggested settings for external.conf does not work for me. lighttpd doesnt start and will only start if i dont have those settings in external.conf
my lets encrypt cert is for a fqdn and i also changed pihole.example.com to my fqdn. error logs dont show anything from what I can see.

Just now I enabled https in both Apache and lighttpd and got both to work, using this howto for PiHole/lighttpd. There's one little issue (if you can even call it an issue) with PiHole/lighttpd.

Apache redirects both http://hostname and http://ip-address to https://hostname, using this howto only the first happens with PiHole/lighttpd.

Is there any way to modify this howto to have http://ip-address redirected to https://hostname as well?

1 Like

This works great. One things to note, if you have the site open in Chrome before you install the cert and restart, then change the URL to HTTPS, it will say the certificate is valid but also display "your connection is insecure."

To fix this, close Chrome and reopen it.

This works well, thanks for the write-up. However, I have a few concerns with the steps presented, one being very serious:

Insecure permissions on private keys.

Like Apache and nginx (and, I suspect, most other web servers), lighttpd reads cert and key files with root permissions, before dropping privileges after launch. Therefore, according to the lighttpd docs:

Be careful to keep your .pem file private! Lighttpd reads all pemfiles at startup, before dropping privileges. It is therefore best to make the pem file owned by root and readable by root only

Unnecessary use of tee

There's absolutely no reason to use tee in the cat command that creates combined.pem (which itself isn't necessary since lighttpd 1.4.53). A simple cat file1 file2 > file3 does the job just fine. This doesn't hurt anything, but just unnecessarily complicates the command.

Incorrect ca-file

The external.conf file has the ssl.ca-file pointing to fullchain.pem, which contains both the "leaf" certificate and the intermediate signing certificate. Again, this isn't likely to break anything, but it'll probably result in an unnecessary cert being sent for every TLS negotiation. The correct file would be chain.pem.

So, for anyone who might care, here's how I modified/updated these instructions to suit my system:


Like another poster up-thread, I use acme.sh for my ACME client for most purposes. It generally separates the action of issuing the cert and installing it, like this:
acme.sh --issue --dns dns_acmedns --dnssleep 5 -d pihole.yourdomain
acme.sh --install-cert -d pihole.yourdomain --cert-file /etc/ssl/certs/cert.pem --ca-file /etc/ssl/certs/ca.pem --key-file /etc/ssl/private/privkey.pem --reloadcmd "cat /etc/ssl/certs/cert.pem /etc/ssl/private/privkey.pem > /etc/ssl/private/combined.pem && systemctl restart lighttpd"

I'm placing the certs and key in /etc/ssl, which is the "default" location for them under Ubuntu. CentOS, IIRC, places them in /etc/pki/tls, other OSs may do otherwise. But it's lately being seen as a "best practice" to put your cert files where the OS would generally expect them to be, rather than directly using your ACME client's cert store.

Now, make combined.pem readable by root only:
chmod 600 /etc/ssl/private/combined.pem

Download DH parameters recommended by the Mozilla config generator:
curl https://ssl-config.mozilla.org/ffdhe2048.txt > /etc/ssl/certs/dhparam

Then, given these changes, the external.conf file looks like this:

$HTTP["host"] == "pihole.yourdomain" {
  # Ensure the Pi-hole Block Page knows that this is not a blocked domain
  setenv.add-environment = ("fqdn" => "true")

  # Enable the SSL engine with a LE cert, only for this specific host
  $SERVER["socket"] == ":443" {
    ssl.engine = "enable"
    ssl.pemfile = "/etc/ssl/private/combined.pem"
    ssl.ca-file =  "/etc/ssl/certs/ca.pem"

    # curl https://ssl-config.mozilla.org/ffdhe2048.txt > /path/to/dhparam
    ssl.dh-file               = "/etc/ssl/certs/dhparam"

    # intermediate configuration
    ssl.openssl.ssl-conf-cmd = ("Protocol" => "ALL, -SSLv2, -SSLv3, -TLSv1, -TLSv1.1")
    ssl.cipher-list           = "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384"
    ssl.honor-cipher-order    = "disable"
}

  # Redirect HTTP to HTTPS
  $HTTP["scheme"] == "http" {
    $HTTP["host"] =~ ".*" {
      url.redirect = (".*" => "https://%0$0")
    }
  }
}
1 Like

Guys, I have a problem here. I followed the manual to install SSL on my pihole's lighttpd webserver. It was a struggle at first, but it worked after trying for a while. I have an end user ssl experience. Later after 1 week, I experience a weird problem when browsing with my pihole.
Some sites, not many, respond with a " Your connection is not private" message, saying there is no SSL connection to the site.....Now going back in the instructions on this site, there is an issue mentioned in case of a faulty SSL configuration at pihole. I thought ssl configuration is only for MY webserver, but it seems in the site here, I was mistaken.

........." Blindly enabling HTTPS for your Pi-hole Web Interface via Let’s Encrypt or a Self-Signed certificate causes issues such as:

** Browsing slowdowns on any site visited, as blocked content needed to time out (or load infinitely)*
** Web Browser errors, such as mismatched certificates*
** Operating system popups on macOS/iOS devices on every site containing blocked content*
......."
Well, I have the line :

$HTTP["host"] == "server.me" {

setenv.add-environment = ("fqdn" => "true")

"
in my external.conf

I confirmed my hostname is the same in this config for 'server.me'
And I don't understand why then most sites work perfect and only a few do not.
It is painful, but when I bypass the pihole and surf directly to the same server on the net, it works again. An example: https://go.skimresources.com/
Gives a certificate error. Is there any other explanation for this? Note, I have tried to add this domain to the whitelist of pihole. Doesn't work either which makes me feel desperated....

Can somebody help me here?
Thanks.

UPDATE:

I made a mistake in whitelisting. Weird, the site https://go.skimresources.com/
works now. I must have a problem with all go.(something) content , cause in my whitelist there ar emore starting like that. Maybe a single blocklist link ...let's see...

UPDATE:
found it, it was in this blocklist:

I the problem still occurs I will ask later. Sorry for any wasted time

Not sure why but things seem to be failing when using IPv6.

Background:
0. Pihole running on small IntelNUC connected to router
12. Have self signed cert for my desired domain (pihole.ipv6.example.com)
2. Dont know what is the "CA-file" line supposed to be so just dropped it.

Testing 1 - When connecting to internal ipv4 IP - get red padlock in chrome and can read off the details of the certificate fine (ie it was self signed and for domain pihole.v6.example.com). So pihole box is reading/serving certificate ok on ipv4.

Testing 2 - connection via IPv6 address fails with "ERR CONNECTION REFUSED" so connecting to pihole box no longer works on ipv6 (note before changes with empty external.conf, connection via ipv6 was fine)

Testing 3 - When connecting to box via FQDN (which is AAAA to the IPv6 address of box) then get connection refused (ie same result as Testing 2)

Testing 4 - When I poke a hole through router firewall to port-forward [[some random high port]] to 443 on the pihole box (and set up a temporary pihole.v4.example.com with A record pointing to router) then connection works (of course with red padlock and complaint that the SNI is wrong.)

From the testing above - seems like for some reason lighttpd ssl engine doesn't like IPv6? Any thoughts on how to fix?

For SSL/TLS certs it doesnt matter what transport method (IPv4 or IPv6) is used.
Only thing for certs that matter are subject common name (CN), validity, subject alternative Name (SAN) if any, and if time/date at both ends is correct:

pi@noads:~ $ openssl x509 -in pi.hole.crt -noout -text
[..]
        Subject: C = AU, ST = Some-State, O = Internet Widgits Pty Ltd, CN = noads.dehakkelaar.nl
[..]
        Validity
            Not Before: Sep  8 20:18:09 2019 GMT
            Not After : Sep  5 20:18:09 2029 GMT
[..]
            X509v3 Subject Alternative Name:
                DNS:pi.hole
[..]

pi@noads:~ $ timedatectl
      Local time: Sun 2020-05-10 20:24:57 CEST
  Universal time: Sun 2020-05-10 18:24:57 UTC
        RTC time: n/a
       Time zone: Europe/Amsterdam (CEST, +0200)
 Network time on: yes
NTP synchronized: yes
 RTC in local TZ: no

Sounds like wrongly configured lighttpd or IPv6.
But as this is not a lighttpd support forum, you have to be lucky if someone can help you out here.
Am not that proficient with IPv6 yet :wink:

Just realized, you cant change domain name from:

pihole.v4.example.com

into:

pihole.ipv6.example.com

without adjusting the certs common name (CN).

Or you could use SAN and have as many domain aliases as you like in the cert:

EDIT:
http://apetec.com/support/GenerateSAN-CSR.htm

From what I understand elsewhere, I think the common name approach is deprecated and the CA/Browser Forum folks are pushing for the alterative you linked so just using CN will still get the red padlock / warning. Anyway, issue is not the cert; I am fairly certain if I can get to connect via the right SNI it would show as secure.

Let me see if easily available lighttpd place to ask.

Aha, didnt know was depreciated.
Configuring for SNI seems simple enough with lighttpd.
That "ERR CONNECTION REFUSED" error might imply lighttpd not listening on ipv6:

pi@noads:~ $ sudo netstat -nltup | grep 'Proto\|:80 '
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name
tcp        0      0 0.0.0.0:80              0.0.0.0:*               LISTEN      1880/lighttpd
tcp6       0      0 :::80                   :::*                    LISTEN      1880/lighttpd

Or maybe firewall blocking:

pi@noads:~ $ sudo iptables -nL
Chain INPUT (policy ACCEPT)
target     prot opt source               destination

Chain FORWARD (policy ACCEPT)
target     prot opt source               destination

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination

EDIT: ow ps. default Pi-hole install without vhost configured for 443 wont connect either:

pi@noads:~ $ nc -vz $(hostname -I) 443
nc: connect to 10.0.0.2 port 443 (tcp) failed: Connection refused

pi@noads:~ $ curl -Iv https://$(hostname -I)
* Rebuilt URL to: https://10.0.0.2/
*   Trying 10.0.0.2...
* TCP_NODELAY set
* connect to 10.0.0.2 port 443 failed: Connection refused
* Failed to connect to 10.0.0.2 port 443: Connection refused
* Closing connection 0
curl: (7) Failed to connect to 10.0.0.2 port 443: Connection refused

Just checked - listening on 80 for both ipv4/ipv6 but only 443 for ipv4. From the external.conf in the OP there is a redirect from http/https and so I think that is working and pushing me to 443 which results in the connection refused. Basically need to set up listening on ipv6 443 (its an odd default since 80 is switched on and fine but 443 is left out. Yippe for adoption and equal treatment of ipv6)

I've put to lighttpd folks in their respective forum but if anyone here knows would be much obliged.

Can you post output for below ?

sudo netstat -nltup | grep 'Proto\|:80 '

EDIT: darn you right, my netstat doesnt listen to 443 either.
I guess you need to setup a valid vhost for 443 before starts listening.

Here she is:

pi@noads:~ $ less /etc/lighttpd/lighttpd.conf
[..]
include_shell "/usr/share/lighttpd/use-ipv6.pl " + server.port

pi@noads:~ $ cat /usr/share/lighttpd/use-ipv6.pl
#! /usr/bin/perl -w

use Socket;
use strict;

my $sock;
my $PORT = 80;
$PORT = $ARGV[0] if $ARGV[0] and $ARGV[0] >= 0 and $ARGV[0] <= 65535;

if (socket($sock, AF_INET6, SOCK_STREAM, 0)) {
    print qq/\$SERVER["socket"] == "[::]:$PORT" { }\n/;
}

EDIT: wonder if you can add a port.

Yep. Looks like isolated out the reason - c.f. netstat reporting on listening 80 and 443:

80:

tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN 12375/lighttpd
tcp6 0 0 :::80 :::* LISTEN 12375/lighttpd

443:

tcp 0 0 0.0.0.0:443 0.0.0.0:* LISTEN 12375/lighttpd

Can you connect now with curl or nc ?

Hmm above says only listening 443 for IPv4 and not IPv6.

EDIT: can try change port in below file for fun :wink:

/usr/share/lighttpd/use-ipv6.pl

Plus:

sudo service lighttpd restart

not dice on ths - tried changing 80 to 443 and after restart of lighttpd service the netstat still the same. Perhaps reading another config file.

Anyway - gotta get going on work now; look at this in evening.

Looks like the lighttpd guys came through. Need to have additional section below in external.conf. Tested and it makes IPv6 work.

Thanks to gstrauss (redmine.lighttpd.net) for solving this.

@wally3k - perhaps add this to the block with the change suggested as this should applicable to all

$SERVER["socket"] == "[::]:443" {
ssl.engine = "enable"
ssl.pemfile = "/home/username/combined.pem"
ssl.honor-cipher-order = "enable"
ssl.cipher-list = "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH"
ssl.use-sslv2 = "disable"
ssl.use-sslv3 = "disable"
}

1 Like

Yeah you beat me to it.
Was bout to post below through trial error :wink:

pi@noads:~ $ sudo netstat -nltup | grep 'Proto\|lighttpd'
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name
tcp        0      0 0.0.0.0:80              0.0.0.0:*               LISTEN      5977/lighttpd
tcp6       0      0 :::80                   :::*                    LISTEN      5977/lighttpd

pi@noads:~ $ sudo mkdir /etc/lighttpd/certs
pi@noads:~ $

pi@noads:~ $ cat pi.hole.crt pi.hole.key | sudo tee /etc/lighttpd/certs/pi.hole.pem
[..]

pi@noads:~ $ sudo chmod 600 /etc/lighttpd/certs/pi.hole.pem
pi@noads:~ $

pi@noads:~ $ sudo nano /etc/lighttpd/external.conf
$HTTP["host"] == "noads.dehakkelaar.nl" {
  # Ensure the Pi-hole Block Page knows that this is not a blocked domain
  setenv.add-environment = ("fqdn" => "true")

  # Enable the SSL engine with a LE cert, only for this specific host
  $SERVER["socket"] == ":443" {
    ssl.engine = "enable"
#    ssl.pemfile = "/etc/letsencrypt/live/pihole.example.com/combined.pem"
#    ssl.ca-file =  "/etc/letsencrypt/live/pihole.example.com/fullchain.pem"
    ssl.pemfile = "/etc/lighttpd/certs/pi.hole.pem"
    ssl.honor-cipher-order = "enable"
    ssl.cipher-list = "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH"
    ssl.use-sslv2 = "disable"
    ssl.use-sslv3 = "disable"
  }
  $SERVER["socket"] == "[::]:443" {
    ssl.engine = "enable"
#    ssl.pemfile = "/etc/letsencrypt/live/pihole.example.com/combined.pem"
#    ssl.ca-file =  "/etc/letsencrypt/live/pihole.example.com/fullchain.pem"
    ssl.pemfile = "/etc/lighttpd/certs/pi.hole.pem"
    ssl.honor-cipher-order = "enable"
    ssl.cipher-list = "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH"
    ssl.use-sslv2 = "disable"
    ssl.use-sslv3 = "disable"
  }


  # Redirect HTTP to HTTPS
  $HTTP["scheme"] == "http" {
    $HTTP["host"] =~ ".*" {
      url.redirect = (".*" => "https://%0$0")
    }
  }
}

pi@noads:~ $ sudo service lighttpd restart
pi@noads:~ $

pi@noads:~ $ sudo netstat -nltup | grep 'Proto\|lighttpd'
Proto Recv-Q Send-Q Local Address           Foreign Address         State
tcp        0      0 0.0.0.0:80              0.0.0.0:*               LISTEN
tcp        0      0 0.0.0.0:443             0.0.0.0:*               LISTEN
tcp6       0      0 :::80                   :::*                    LISTEN
tcp6       0      0 :::443                  :::*                    LISTEN

pi@noads:~ $ curl -Ivk https://[::1]
* Rebuilt URL to: https://[::1]/
*   Trying ::1...
* TCP_NODELAY set
* Connected to ::1 (::1) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* Cipher selection: ALL:!EXPORT:!EXPORT40:!EXPORT56:!aNULL:!LOW:!RC4:@STRENGTH
* successfully set certificate verify locations:
*   CAfile: /etc/ssl/certs/ca-certificates.crt
  CApath: /etc/ssl/certs
* TLSv1.2 (OUT), TLS header, Certificate Status (22):
* TLSv1.2 (OUT), TLS handshake, Client hello (1):
* TLSv1.2 (IN), TLS handshake, Server hello (2):
* TLSv1.2 (IN), TLS handshake, Certificate (11):
* TLSv1.2 (IN), TLS handshake, Server key exchange (12):
* TLSv1.2 (IN), TLS handshake, Server finished (14):
* TLSv1.2 (OUT), TLS handshake, Client key exchange (16):
* TLSv1.2 (OUT), TLS change cipher, Client hello (1):
* TLSv1.2 (OUT), TLS handshake, Finished (20):
* TLSv1.2 (IN), TLS change cipher, Client hello (1):
* TLSv1.2 (IN), TLS handshake, Finished (20):
* SSL connection using TLSv1.2 / ECDHE-RSA-AES256-GCM-SHA384
* ALPN, server did not agree to a protocol
* Server certificate:
*  subject: C=AU; ST=Some-State; O=Internet Widgits Pty Ltd; CN=noads.dehakkelaar.nl
*  start date: Sep  8 20:18:09 2019 GMT
*  expire date: Sep  5 20:18:09 2029 GMT
*  issuer: C=AU; ST=Some-State; O=Internet Widgits Pty Ltd; CN=noads.dehakkelaar.nl
*  SSL certificate verify result: self signed certificate (18), continuing anyway.
> HEAD / HTTP/1.1
> Host: [::1]
> User-Agent: curl/7.52.1
> Accept: */*
>
< HTTP/1.1 200 OK
HTTP/1.1 200 OK
< Content-type: text/html; charset=UTF-8
Content-type: text/html; charset=UTF-8
< Date: Mon, 11 May 2020 03:13:38 GMT
Date: Mon, 11 May 2020 03:13:38 GMT
< Server: lighttpd/1.4.45
Server: lighttpd/1.4.45

<
* Curl_http_done: called premature == 0
* Connection #0 to host ::1 left intact

EDIT: added -k argument for curl for self signed certs.

2 Likes

double thumbs up for the learning through doing (for me, learning through googling hahahha)

slightly OT but perhaps something you can look into if you have a moment; does the conf file syntax support an "OR" operator coz right now it is a bit messy to have two blocks of code exactly the same if it possible to instead do:

$SERVER["socket"] == ":443" ((OR)) "[::]:443"

1 Like