If you had visited my blog a few weeks back, it must have looked different, loaded slowly and there was a ton of crappy ads and it was having only basic security features.
Before this transition, the blog was using a Self-hosted version of Wordpress on a 10 USD/month Digital Ocean droplet (2GB RAM, 1 CPU, 50GB SSD disk) served via an Apache server serving over https using free TLS (or as people generally say, SSL) certificates provided by LetsEncrypt.
The DNS was handled by Cloudflare but I was not using any of there other features because of a known issue during certificate renewals via LetsEncrypt.
I've also made the WordPress blog as a PWA (Progressive Web App) with the help of Service workers and it also had Onesignal push notifications enabled.
Issues with the old setup
- Even though the site had https implemented, sometimes there will be mixed content, SSL test grades will be low and were supporting old and vulnerable TLS versions such as TLSv1.0 & TLSv1.1.
- Lighthouse report ratings were very poor.
- It was not IPv6 compatible.
- I've also enabled Google Ads which used to distort the site in uglier ways which you can't even imagine and the total income earned from it so far was just $1.31.
- The site was slow to load despite the Wordpress level optimizations I have done.
- Maintaining the PHP versions, Wordpress & Plugin versions, MySQL server was a nightmare.
- There was no HTTP/2 support, no gzip compression, etc.
- Since Wordpress had many known vulnerabilities hackers / even script kiddies were constantly trying to hack the site using some scripts.
- Even the $10 server was not able to handle the setup due to insane traffic from bots, hack attempts and other PHP, MySQL & Apache related issues.
- There were no Backups of the Wordpress site and DB.
Migration to the new setup
To mitigate the above-mentioned issues and also to experiment, I've decided to do something about it and first came up with the below objectives and performed the improvements keeping those objectives in mind.
- Fast & Responsive - I've decided that the blog should load very fast on all devices and all connection speeds.
- Safe & Secure - The blog should be very secure and should employ all the best security practices and get an A+ on the SSL Labs test and should support the latest protocols.
- Cheaper & Easier - The blog should fulfill the above objectives without any added cost, possibly reduce the cost as well as easier to maintain.
With these objectives in mind, I performed the below things.
Migration from Wordpress to Hugo
I decided to go with a Static Site Generator because I can easily create blog posts using Markdown and I use markdown almost every day and also Static pages are fast, secure and can be cached locally as well as using some CDN like CloudFlare.
The hardest task was to decide on a good Static Site Generator. After considering Jekyll & Hugo, I decided to go with Hugo as it was recommended by many colleagues, mostly because it's written in Go (They are big fans of Go).
There was a bit of learning curve but somehow I got it (I think.). And the next hardest part was to convert your Wordpress posts into Markdown format.
Wordpress XML to MD conversion
First, I exported the Wordpress posts into XML format and then for converting into proper Markdown, I tried several easy methods, none of them worked. Then I followed this article to successfully convert the XML to MD using blog2md tool.
The blog2md tool did a decent job but still, I had to edit a lot of small things in the posts and also I merged the comments into the post as well (may remove it later).
Since, I had a lot of posts, editing them individually took a lot of time and I also updated a few posts with the latest details.
Backups on GitHub
Previously when I was using Wordpress, once in a while I'll take DB backups and files and I'll keep them directly on my DO droplet itself, there were no other backup or Source Code Management done. All changed were done directly done on the live site.
This time I decided to use Github as SCM as well as backups, I'll develop and test locally and then will compile the files locally and push it to Github.
There is no CI/CD pipeline set up yet. As of now, I just SFTP the compiled pages directly to the server and Github merely serves as a backup and SCM tool.
Replaced Apache with Nginx
I did a lot of research on whether to continue using Apache or switch to Nginx. Based on my research I did for a client earlier, I already fell in love with Nginx and decided to go with it, as it is faster and also it can perform a lot of other things.
So, I installed Nginx 1.17.7 on a new DO droplet with fewer resources and configured it for my domain. By the time, I finished writing this article, Nginx 1.17.8 got released and I updated it. I took almost (or maybe more than) a month to finish this article.
SSL certificates from LetsEncrypt
As usual, I decided to use LetsEncrypt for generating the certificates, so installed the certbot for Nginx.
I changed the A record of the domain to the latest server and generated the TLS certificate and uploaded the compiled static files and saw the https certificate from LetsEncrypt authority.
Enabled HTTP/2, gzip compression & IPv6
To make the site faster and secure, I enabled HTTP/2, gzip compression and also enabled IPv6 support.
Getting an A+ from SSL Labs
I was obsessed with making the site secure and getting an A+ on SSL Labs. So, I performed the below steps to get an A+ certificate.
Setup CAA Records
As stated earlier, I was managing my DNS using CloudFlare and I had set up a CAA record for
letsencrypt.org from there. This will prevent other CA's from issuing certificates for my domain.
Revoked weak & vulnerable TLS Suites
As per the suggestions provided in the SSL labs, I revoked support for weak TLS suites such as TLSv1.0 and TLSv1.1 in the
Experimental TLSv1.3 support & TLS Fallback to TLSv1.2
I have also enabled support for the experimental and more secure TLSv1.3 and a TLS Fallback to TLSv1.2 in the
#ssl_protocols TLSv1 TLSv1.1 TLSv1.2; ssl_protocols TLSv1.2 TLSv1.3; ssl_prefer_server_ciphers on;
Then I enabled OCSP Stapling to improve the security and performance in the
## OCSP Stapling ssl_stapling on; ssl_stapling_verify on;
Replaced Weak Ciphers with Strong Ciphers
To improve security, I have replaced weak ciphers with Strong ciphers while also managing to support maximum devices.
Expire Headers for Caching
To make the blog faster for regular visitors, I had setup expires header to cache locally in the browser as below.
# Setting expires headers (Inside Server block) expires $expires;
I also wanted the site to be always preloaded via https and want it included in the hstspreload list of the most common browsers, so it is hardcoded in the browsers to always load the site via https even when the request is made by the client for the first time.
So, I have added the below config for HSTS preloading and also submitted it to hstspreload.org website.
#HSTS Preload add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload";
By the time I finished writing the article, the site got accepted into the HSTS Preload list.
Proxied via CloudFlare
Even though I was using CloudFlare for DNS management, I didn't proxy the site via CloudFlare and didn't make use of all the benefits offered by CloudFlare due to a known issue in certificate renewals.
This time I decided to take advantage of the benefits offered by CloudFlare despite the known issue.
Benefits of CloudFlare proxying
- CloudFlare proxying hides the identity of the original server.
- Provides support for QUIC (HTTP/3).
- Advanced Caching.
- Spam detection & prevention.
- Acts as a global CDN and reduces the load on the origin server.
- Automatic Threat mitigation.
- Improves performance & Speed
- Inbuilt Firewall & Service workers and a lot more.
- Advanced security configurations.
- And most importantly, almost all of these features are available for free by default.
The reason that I initially didn't want to use CloudFlare proxying is that there was a known issue while generating/renewing your certificates via LetsEncrypt because of proxying, the origin server was not exposed.
So, every time for renewals, I had to disable proxying, renew the certificate and then again enable it. It was a bit of a pain. But I'm ready to endure it this time because the pros are a lot more significant comparing this issue.
However, a colleague suggested a potential solution to resolve this issue.
By default, LetsEncrypt certbot uses
HTTP-01 challenge to perform validation and that's why I'm facing this issue due to the hiding of the original server address. However, I'm told it can be replaced by the
DNS-01 challenge to mitigate this issue and renew certificates while proxying via CloudFlare.
I'll try to attempt this soon and will update it in a separate blog post.
When I enabled CloudFlare proxying, some of the settings configured in Nginx (as mentioned above) got overwritten by the default CloudFlare configurations which didn't comply with the hstspreload required config & caching settings.
So, I had to modify them to comply with the hstspreload requirements (similar to the Nginx config mentioned above) and also enabled few other settings which are listed below.
- Enabled QUIC
- HSTS settings
- Always online
The Domain Name System Security Extensions (DNSSEC) attempts to add security and was designed to protect applications (and caching resolvers serving those applications) from using forged or manipulated DNS data, such as that created by DNS cache poisoning. All answers from DNSSEC protected zones are digitally signed.
I have enabled DNSSEC in the CloudFlare configuration and followed the instructions to set up DNSSEC at my domain registrar (Google Domains).
Halved the Droplet resources
Due to the above-mentioned changes & optimizations, the bulk of the load will be taken care of CloudFlare and only Static pages will be served by our server, the resources required are not much, so I halved the server capacity from that of the previous one.
So, Instead of the previous $10/month server (2GB RAM, 50GB SSD HDD, 2TB transfer), I used a minimum droplet costing just $5/month (1GB RAM, 25GB SSD HDD, 1000GB transfer) and it barely utilizes 1/3rd of its resources in terms of CPU & RAM usage.
Though It's a given that my blog doesn't have much (or any) traffic, the previous setup was not able to handle that and used to crash at times.
After performing these changes, I measured the performance of the site using Google Lighthouse and the reports indicate that site performance has increased multifold and is near perfect as shown below, proving the site has become faster.
SSL labs test has revealed a security rating of A+, the highest rating, hence more secure.
It also halved the resources thereby reducing the cost by half but still more than enough resources for the blog to handle a multifold increase in traffic, hence cheaper.
Potential Future Improvements
- Implement the potential solution for the known issue.
- PWA support (Possibly using CloudFlare workers)
- Onesignal notification support
- Some commenting plugin (Mostly Disqus, if not, some opensource alternatives)
- Automated deployments (Possibly using GitHub Actions & rsync)
Top comments (3)
The TL;DR: migrate away from wordpress :D
Yes, preferably to a Static Site Generator and employ best practices everywhere.
Great details thanks. How about ecommerce sites? Any suggestions?