While researching ways to improve the speed of this website, I recently found out that Cloudflare offers a free tier of service to personal websites and blogs. Everything I read made this seem like a pretty easy move and the benefits of increased security, speed, and DOS protection seemed like this was a good decision.

The decision was a good one and the benefits have materialized. That being said, I have had to learn a lot and full implementation has been a little complicated in trying to make my server as secure as possible. I did not know all of this was possible at the outset but here is what I have been able to do with Cloudflare.

Cloudflare Settings

Initially setting up Cloudflare is really easy. Simply sign up for an account, point your current domain records to the IPs provided by Cloudflare, and then allow Cloudflare to manage your DNS. This takes about 24 hours but does not require any down time on your website.

At this point hitting your domain directly will route all requests through the Cloudflare CDN with whatever options you have configured in their admin panel. In my case, the only major consideration I had to make was with respect to Crypto/SSL. Since I was running Let's Encrypt Certs on my page I had to configure Full (Strict) mode (I have since changed this, more on that later).

RocketLoader

I also enabled most of the speed improving capabilities of Cloudflare. The only issue I noticed at this point was that PrismJS (a code syntax highlighting JS module) kept kicking back 404 errors in my web logs. If I put Cloudflare in Development (bypass) mode, I did not get the errors. After weeks of troubleshooting locally, I finally discovered that the RocketLoader feature is in Beta and apparently has some issues with PrismJS. Disabling RocketLoader solved that problem.

Client Cert

While perusing the Cloudflare settings (and wanting to enable all the security related ones), I found a setting for Authenticated Origin Pulls. This setting passes a certificate in all requests directly to your server so that you can validate that the requests come from CLoudflare and nowhere else. This requires local configuration changes to your web server config.

The complication here is that I do some management of my website through an SSH tunnel so as not to expose my management interface. As such, Authenticated Origin Pulls will fail when accessing my page from local host with the recommended config changes.

The update to make this work with Cloudflare is to set up different server blocks (in NGINX as least) for the primary website and for the admin portion much like this ServerFault article.

The block that you do NOT want to do client certificate checking on needs to come first in your config but requires no other changes (this took a few hours of troubleshooting). For all other server blocks, you will need to add these lines:

ssl_client_certificate /etc/ssl/certs/cloudflare.crt;
ssl_verify_client on;

Then you will need to save the Cloudflare Public Key cert to the location listed above (or a location of your choosing). Restart your NGINX service and you should be good to enable this setting in your Cloudflare panel.

SSL Configuration

Also while perusing the security and crypto settings for Cloudflare I realized that they present certificates for SSL/TLS and that users never actually see my server certificate. In Full (strict) mode, Cloudflare still validates my cert but there is no real benefit here. I also tend to have major issues renewing my Let's Encrypt certs (I think due to my web server config).

Because of this, I decided to ditch Let's Encrypt just use self-signed certs on my server end and rely on Cloudflare to present that valid certs to users. I just followed this Digital Ocean guide to configure my server with self-signed certs.

openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout /etc/ssl/private/nginx-selfsigned.key -out /etc/ssl/certs/nginx-selfsigned.crt

And update these lines in the my NGINX config:

ssl_certificate /etc/ssl/certs/nginx-selfsigned.crt;
ssl_certificate_key /etc/ssl/private/nginx-selfsigned.key;

Finally, set your Cloudflare SSL setting to Full (ignore certificate errors).

IPtables

At this point, everything was pretty secure and squared away through Cloudflare but my website was still accessible directly via IP. I already had a good set of rules for iptables from here but they did not take into account this Cloudflare configuration. To update I needed to add these lines to my iptables rules:

# Accept HTTP and HTTPS from CLoudflare IPs only
-4 -A INPUT -m set --match-set cf4 src -p tcp -m multiport --dports http,https -j ACCEPT
-6 -A INPUT -m set --match-set cf6 src -p tcp -m multiport --dports http,https -j ACCEPT
# Continue to allow localhost access
-4 -A INPUT -i lo -p tcp -m multiport --dports http,https -j ACCEPT

Then to create and keep the IP sets up to date I followed the steps here I rolled those steps into a script that I will run as a cron job on a weekly basis to ensure I get any updates.

echo "Destorying old IP Sets"
ipset destroy cf4
ipset destroy cf6

echo "Creating new IP Sets"
ipset create cf4 hash:net
ipset create cf6 hash:net family inet6

echo "Getting latest lists from CLoudflare"
for x in $(curl https://www.cloudflare.com/ips-v4); do ipset add cf4 $x; done
for x in $(curl https://www.cloudflare.com/ips-v6); do ipset add cf6 $x; done

echo "Reload IP tables"
iptables-restore
ip6tables-restore

Conclusion

All of this configuration should force all traffic to my server through Cloudflare except for traffic that I am tunneling over SSH to the server directly. I am taking advantage of as much of the security and speed improvements as I can from Cloudflare. I have also learned a TON more about server configs and using a CDN.

Cloudflare Client Certificate

-----BEGIN CERTIFICATE-----
MIIGBjCCA/CgAwIBAgIIV5G6lVbCLmEwCwYJKoZIhvcNAQENMIGQMQswCQYDVQQG
EwJVUzEZMBcGA1UEChMQQ2xvdWRGbGFyZSwgSW5jLjEUMBIGA1UECxMLT3JpZ2lu
IFB1bGwxFjAUBgNVBAcTDVNhbiBGcmFuY2lzY28xEzARBgNVBAgTCkNhbGlmb3Ju
aWExIzAhBgNVBAMTGm9yaWdpbi1wdWxsLmNsb3VkZmxhcmUubmV0MB4XDTE1MDEx
MzAyNDc1M1oXDTIwMDExMjAyNTI1M1owgZAxCzAJBgNVBAYTAlVTMRkwFwYDVQQK
ExBDbG91ZEZsYXJlLCBJbmMuMRQwEgYDVQQLEwtPcmlnaW4gUHVsbDEWMBQGA1UE
BxMNU2FuIEZyYW5jaXNjbzETMBEGA1UECBMKQ2FsaWZvcm5pYTEjMCEGA1UEAxMa
b3JpZ2luLXB1bGwuY2xvdWRmbGFyZS5uZXQwggIiMA0GCSqGSIb3DQEBAQUAA4IC
DwAwggIKAoICAQDdsts6I2H5dGyn4adACQRXlfo0KmwsN7B5rxD8C5qgy6spyONr
WV0ecvdeGQfWa8Gy/yuTuOnsXfy7oyZ1dm93c3Mea7YkM7KNMc5Y6m520E9tHooc
f1qxeDpGSsnWc7HWibFgD7qZQx+T+yfNqt63vPI0HYBOYao6hWd3JQhu5caAcIS2
ms5tzSSZVH83ZPe6Lkb5xRgLl3eXEFcfI2DjnlOtLFqpjHuEB3Tr6agfdWyaGEEi
lRY1IB3k6TfLTaSiX2/SyJ96bp92wvTSjR7USjDV9ypf7AD6u6vwJZ3bwNisNw5L
ptph0FBnc1R6nDoHmvQRoyytoe0rl/d801i9Nru/fXa+l5K2nf1koR3IX440Z2i9
+Z4iVA69NmCbT4MVjm7K3zlOtwfI7i1KYVv+ATo4ycgBuZfY9f/2lBhIv7BHuZal
b9D+/EK8aMUfjDF4icEGm+RQfExv2nOpkR4BfQppF/dLmkYfjgtO1403X0ihkT6T
PYQdmYS6Jf53/KpqC3aA+R7zg2birtvprinlR14MNvwOsDOzsK4p8WYsgZOR4Qr2
gAx+z2aVOs/87+TVOR0r14irQsxbg7uP2X4t+EXx13glHxwG+CnzUVycDLMVGvuG
aUgF9hukZxlOZnrl6VOf1fg0Caf3uvV8smOkVw6DMsGhBZSJVwao0UQNqQIDAQAB
o2YwZDAOBgNVHQ8BAf8EBAMCAAYwEgYDVR0TAQH/BAgwBgEB/wIBAjAdBgNVHQ4E
FgQUQ1lLK2mLgOERM2pXzVc42p59xeswHwYDVR0jBBgwFoAUQ1lLK2mLgOERM2pX
zVc42p59xeswCwYJKoZIhvcNAQENA4ICAQDKDQM1qPRVP/4Gltz0D6OU6xezFBKr
LWtDoA1qW2F7pkiYawCP9MrDPDJsHy7dx+xw3bBZxOsK5PA/T7p1dqpEl6i8F692
g//EuYOifLYw3ySPe3LRNhvPl/1f6Sn862VhPvLa8aQAAwR9e/CZvlY3fj+6G5ik
3it7fikmKUsVnugNOkjmwI3hZqXfJNc7AtHDFw0mEOV0dSeAPTo95N9cxBbm9PKv
qAEmTEXp2trQ/RjJ/AomJyfA1BQjsD0j++DI3a9/BbDwWmr1lJciKxiNKaa0BRLB
dKMrYQD+PkPNCgEuojT+paLKRrMyFUzHSG1doYm46NE9/WARTh3sFUp1B7HZSBqA
kHleoB/vQ/mDuW9C3/8Jk2uRUdZxR+LoNZItuOjU8oTy6zpN1+GgSj7bHjiy9rfA
F+ehdrz+IOh80WIiqs763PGoaYUyzxLvVowLWNoxVVoc9G+PqFKqD988XlipHVB6
Bz+1CD4D/bWrs3cC9+kk/jFmrrAymZlkFX8tDb5aXASSLJjUjcptci9SKqtI2h0J
wUGkD7+bQAr+7vr8/R+CBmNMe7csE8NeEX6lVMF7Dh0a1YKQa6hUN18bBuYgTMuT
QzMmZpRpIBB321ZBlcnlxiTJvWxvbCPHKHj20VwwAz7LONF59s84ZsOqfoBv8gKM
s0s5dsq5zpLeaw==
-----END CERTIFICATE-----