WordPress Hardening with Cloudflare

Now the site is under protection of Cloudflare. Some settings:

1. Cloudflare as CDN

Cloudflare DNS to configured to proxy the original server. The DNS is also served as CDN. It also adds IPv6, HTTP2 & HTTP3 support. Cloudflare IPs are whitelisted in the inbound rules of my upstream VPS. mod_remoteip is enabled in Apache2, and configure with RemoteIPHeader X-Forwarded-For for logging.

Go to SSL/TLS / Overview, set SSL/TLS encryption mode to Full (strict).

1.1 Redirect Rule: Redirect from root to www

Go to Rules/Overview to add a cache rule.

NOTE: wordpress does redirect root URL to www URL internally, to align with the Site Address settings in Settings/General. It can be observed by:

I just delegated this job to Cloudflare.

2. Security Settings

2.1 Security Headers

In Rules/Settings, check Add security headers. It adds 3 headers in response: X-Content-Type-Options: nosniff, X-Frame-Options: SAMEORIGIN and X-XSS-Protection: 1; mode=block.

2.2 WAF Rule: Challenge empty referer

Only pseudo code is used to describe the rules, to keep it a secret from web crawlers. Go to Security/WAF.

I actually merge several rules into one, since only 5 rules can be added for a free Cloudflare plan. Here, we allow Let's Encrypt to bypass the rule. Their servers renew certificates through plain Http requests.

2.3 WAF Rule: Block xmlrpc.php

Here, we fixed several wordpress security holes by blocking them. xmlrpc.php is only cared by malicious crawlers. wp-login.php is blocked, since I have moved the login entrance to somewhere else. wp-config* is certainly blocked. It may become wp-config.php.txt or wp-config.php.bak one day when you backup the config, and your password is exposed. /wp-admin* is certainly blocked. No wordpress plugin should access it, it is a design defect.

2.4 WAF Rule: Block bots

Here, we block 3 crawlers. Actually only Baidu spider is required, it ignores robots.txt.

2.5 WAF Rule: Block flood

This is a rate limiting rule. Only 1 rule can be used for a free Cloudflare plan. We limit malicious access to php and uncommon resource file types. Cloudflare does have DDOS protection in the free plan. I just add one more.

3. Cache Settings

3.1 Configure Default Browser Cache TTL

In Caching/Configuration, set Browser Cache TTL to Respect Existing Headers.

3.2 Cache Rule: Cache resources

Go to Rules/Overview to add a cache rule.

Edge TTL specifies how long Cloudflare should cache the response, distinguish from Browser TTL. Default TTL for 404 is 3m, while for 200 is 120m. See here. Resource files in wordpress are request by a version parameter to invalidate themself. 120m is a too short duration for them.
Cache control can also be configured in the original server. But I choose to adopt Cloudflare.

3.3 Cache Rule: Cache major pages

Yes, we cache error pages, to offload the workload of original server. Major pages are defined as root page(/) and individual wordpress post pages. Cloudflare gives 512M cache space for a free plan, see here.
Couldflare does not cache HTML by default, explicit configuration is required, see here (It does cache robots.txt). Cloudflare returns cf-cache-status: DYNAMIC response header by default. With our configuration, it returns cf-cache-status: EXPIRED. A bit strange, but this is my desired behavior. Explanation here:

HIT: The resource was served from the Cloudflare cache
MISS: There was a cache miss and the resource was served from the origin server
DYNAMIC: The resource was not eligible to be cached based on your Cloudflare cache rules
BYPASS: The resource would normally be cached, by was the behavior was overridden by a cache-control header or because the origin server set a cookie
EXPIRED: Cloudflare found a stale resource in the cache and had to fetch it again

Custom filter expression is selected for incoming requests matching. Since All incoming requests also matches php file, which is used by wordpress admin console. It will be a mess if all admin functions are cached.

3.4 Cache Rule: Cache minor pages

Error pages are also cached here. Minor pages are like /tags/... and /category/.... They are updated in 7 days.

4. Conclusion

With all configurations above, the cache hit rate is about 70% – 90% in average. All configurations are monitored and adjusted this week. WordPress is a legacy monolithic application. It is all-purpose and it can do almost anything regarding content management with plugins. It mixes frontend and backend. It mixes content service and content administration. All those mess make it tricky to setup and tweak correctly.

Some alternatives are found:
Ghost: The most ideal one, with some drawbacks. a. It does not use html or markdown for contents. b. Plugin installation may require manual file operations. File overwrites are possible. c. Is a raw customized html page possible?
Kirby: It is lightweight, but I do not want php any more.
Strapi and other headless CMS: not so user friendly or costs time to adapt.

Maybe a simple SSG-based blog is the final destination. No more php, no more CDN, no more endless plugins, no more tricky config and hardening work.

Some WordPress Hardening Work

1. Disable File Access

2. Disable wp-cron.php

See here.

The default method works perfectly fine on a small site with very few visitors per hour. However, when implemented on a medium or larger site or even a site that is being scanned by bots (which is very common these days), this means you get twice fold whatever traffic you are currently handling. It becomes a rudimentary DDoS attack against yourself.

Add define('DISABLE_WP_CRON', true); to wp-config.php.

3. Disable Pingback in Comments

Go to Settings –> Discussion, uncheck Allow link notifications from other blogs (pingbacks and trackbacks) on new posts.

4. Disable Json API

Via Disable WP REST API. Activate and it just works.

5. Hide Login Page

Via WPS Hide Login.

6. Hide Server Info

Via mod_security. Install and add config in /etc/apache2/mods-enabled/security2.conf:

7. Disallow IFrame Embedding

To avoid clickjacking attacks:

8. Add reCAPTCHA

Via Advanced Google reCAPTCHA.

9. Refine robots.txt

Via WP Robots Txt:

The /wp-admin/admin-ajax.php path is allowed by default, see here and here. Simply remove it.
Updated May 8, 2025: It seems Baiduspider ignores robots.txt, since no access log for the file is found in latest 5 years. Simply blocked it in Cloudflare WAF.

10. More Fail2ban Rules

Including 400/403/404 errors, directory listing filters, and subnet bannning.

Uniform look for Qt and GTK applications

See: https://wiki.archlinux.org/title/Uniform_look_for_Qt_and_GTK_applications

Theme: The custom appearance of an application, widget set, etc. It usually consists of a style, an icon theme and a color theme.
Style: The graphical layout and look of the widget set.
Icon Theme: A set of global icons.
Color Theme: A set of global colors that are used in conjunction with the style.

Actually a theme also controls fonts, and native dialogs, like open file dialog. How to write a Qt style is covered here.

I’m running Linuxmint 22 with Arc theme on my desktop. Fusion theme is used by default for Qt applications. They removed qt5ct in Linuxmint 22 in a fresh install. But it seems to be the best solution so far. Following is a comparison among the possible approaches, when run a Qt application. Which means launch a Qt application by:

  has theme? has style? QT_QPA_
PLATFORMTHEME
QT_STYLE_
OVERRIDE
Description
Gtk2 Yes Yes gtk2 gtk2 or empty Good for widgets, indicators in radio button and checkbox can be styled, follows current Gtk theme. But It has HiDPI issues, and certainly not maintained.
Gtk3 Yes No gtk3 or empty values in qt5ct No style plugin. Fusion is used by default, which is not consistent with other themed Gtk applications.
qt5ct Yes Yes qt5ct values in qt5ct A proxy style used, amost no difference to the default style. Fusion is used by default, which is not consistent with other themed Gtk applications. Color scheme and font can be further customized. Button indicators are not styled.
Kvantum Yes Yes qt5ct kvantum Use Kvantum Manager to further customize the theme. Button indicators are styled. KvArc theme is provided, but is still somehow different in visual. Kvantum also installs several KDE component, which is odd.

So my final solution is: using qt5ct with customized color scheme and font. Color scheme defined:

Copy those 2 file into ~/.config/qt5ct/color, open qt5ct:

  • Go to Appearance –> Platte –> Check custom and select arc.
  • Go to Fonts –> Select your Gnome/Cinnamon font.
  • Go to Icon Theme –> Select your Gnome/Cinnamon icon theme.
  • Open ~/.config/qt5ct/qt5ct.conf, change standard_dialogs=default to standard_dialogs=gtk3.

See the difference:
vlc_default
vlc_themed

Updated May 4, 2025, there is a refined arc-theme called Qogir, which provides Qt & KDE themes as well. The Qt theme is based on awaita-qt.

Upgrading Ubuntu 24.04 Network Configuration

Debian/Ubuntu and RHEL/AlmaLinux have different network configuration utilities. RHEL 9 has deprecated ifcfg-files, and adopted NetworkManager. There is no ifup or ifdown any more after a fresh installation. Since my server was first installed using Ubuntu 14.04, it still uses these scripts. Time to move on.

1. ifupdown

Netplan is used to configure networking in Ubuntu 18.04 and later. Ubuntu Server is packaged with systemd-networkd as the backend for Netplan, while NetworkManager is used as the Netplan backend in Ubuntu Desktop. Install by:

Get current network status by:

eth0 is unmanaged, since ifupdown is used. The config file is /etc/network/interfaces.

2. networkd

Create a config file /etc/netplan/50-cloud-init.yaml

This file is create by cloud-init if fresh installed. I kept the name. networkd comes with systemd, no need to install it again. Apply it by:

Now, eth0 should be managed by networkd:

The generated config file can be found in /run/systemd/network/10-netplan-eth0.network. System config files located in /etc/systemd/networkd.conf & /usr/lib/systemd/network/.

3. NetworkManager

NetworkManager can also be used for servers. Install by:

Create a config file /etc/netplan/01-network-manager-all.yaml.

This file is create by Ubuntu installer if fresh installed. I kept the name. Verify the merged config by running:

NOTE, one additional step need to be performed, /etc/network/interfaces must *not* exist. NetworkManager has a plugin to parse the file. Backup it, so that you can roll back to ifupdown if something goes wrong. Apply it by:

Now, eth0 should be managed by NetworManager:

The generated config file can be found in /run/NetworkManager/system-connections/netplan-eth0.nmconnection. System config files located in /etc/NetworkManager/NetworkManager.conf & /usr/lib/NetworkManager/. On systems before RHEL 9, /run may be /var/run. When NetworkManager starts and finds no connection for a device it might create an in-memory connection. No config file is created. The no-auto-default configuration disables that behavior. Check systemd log for details:

More info can be found in Debian documents. Useful commands include: NetworkManager --print-config, nmcli device & nmcli connection.

4. Clean ups

Now, you can safely remove ifupdown, and the networking systemd service will be removed too.

5. iptables

An ifupdown script was add to persist iptables rules.

This can be migrated by installing iptables-persistent:

11 Years of My Site

As the recorded by Linode, the site server was first launched on Apr 1, 2014. It was running Ubuntu 12.04. Later I setup wordpress and moved my blog here from Blogspot on Aug 16, 2024. Spurious Wakeups was my first post. Now, the server is running Ubuntu 24.04, and is using catch box theme with css customization:

Just updated all plugins and found 3 problems.

1. Google Analytics 4

Google fully replaced Universal Analytics with Google Analytics 4 on July 1, 2024. Just noticed that, all visiting history are lost 🙁

2. Fail2ban not working

Fail2ban is not working anymore, due to the switch from iptables to nftables in Ubuntu 21.10. So it has broken for about 2 years, since my last upgrade to Ubuntu 22.04. Found the issue by running fail2ban-client -d. Simply revert the change in config sudo vi /etc/fail2ban/jail.d/defaults-debian.conf.

Since Fail2ban also updated its default backend from pyinotify to systemd(see above), backend should be also reverted in wordpress config sudo vi /etc/fail2ban/jail.d/wordpress.conf.

Make sure package python3-pyinotify and python3-systemd are installed. Restart Fail2ban.

3. Akismet not working

Akismet is reporting 500 errors. Details are found in logs: .../plugins/akismet/.htaccess: Require not allowed here. This is sloved by adding config to apache server to allow AuthConfig, in both http and https config files.

Using vcpkg for C++ Package Management

Verified on CentOS7 and Windows 10.

1. Install v2ray and run proxy

v2ray unblocks github access from mainland China. Install v2ray clients and set IE proxy _only_ on Windows, bootstrap.bat & vcpkg.exe picks it automatically.

2. Download vcpkg from github and bootstrap

Download from: https://github.com/microsoft/vcpkg/releases

Export vcpkg-2022.08.15 directory as ${VCPKG_ROOT}.

3. Install drogon framework for demo

The drogon framework is a high performance application framework, including client & server supports. vcpkg builds static(*.a) library by default, use x64-linux-dynamic for dynamic(*.so) library. The repo version requires g++-8 to build, install from CentOS SCL:

To build with g++-7, manually install boost-filesystem package in vcpkg, and edit ${VCPKG_ROOT}/ports/drogon/portfile.cmake and comment out:

On Windows, open the command line for Visual Studio develop environment.

If openssl build fails, run:

If other errors, try to update to recent github ports. In my case, libmariadb build failed, that have been fixed in master.

4. Export drogon framework

5. Add a demo program

Linux dynamic build is community supported, invoke cmake with:

Now build with make or Visual Studio.

6. Stick to a specific version

add a vcpkg.json file:

It sticks to drogon 1.8.0 and openssl 1.1.1n. ${VCPKG_ROOT} now required to be a git repository. In your project directory, install specific versions of libraries by running:

Run cmake:

Now ldd output shows openssl 1.1 (default build is 3.0):

The only difference is the existence of vcpkg.json file, when using versioning.

7. Binary caching

If you change the root path of vcpkg, better clean up the cache, or build may fail. It’s $HOME/.cache/vcpkg/archives under Linux, and %LOCALAPPDATA%\vcpkg\archives under Windows.

Optimizing Linux Performance in Virtual Machines

Bad performance after upgrading to Linux Mint 20 (Ubuntu 20.04 based). Solved by disabling transparent hugepage in kernel.

The grub approach survives a restart:

Find and replace GRUB_CMDLINE_LINUX line to:

Update grub and reboot:

Updated Oct 4, 2021: if using Windows guest, also use OpenGL render to avoid high CPU usage. Edit *.vmx file and add:

Deploying Kubernetes Cluster on CentOS 7

It is painful to deploying a Kubernetes cluster in mainland China. The installation requires access to Google servers, which is not so easy for every one. Fortunately, there are mirrors or alternative ways. I’ll use Docker v1.13 and Kubernetes v1.11 in the article.

1. Install Docker

CentOS SCL should be enabled first.

2. Install Kubernetes

2.1 Add the Aliyun mirror for Kubernetes packages

2.2 Precheck OS environmemt

Run the init command by specify the version, the access to Google server is avoided. The script also advices you to turn off firewalld, swap, selinux and enable kernel parameters:

Open /etc/sysconfig/selinux, change enforcing to permissive.
Create /etc/sysctl.d/k8s.conf with content:

Remember to comment out swap volumes from /etc/fstab.

2.3 Pull Kubernates images

Pull the Kubernetes images from docker/docker-cn mirror maintained by anjia0532. These are minimal images required for a Kubernetes master installation.

These version numbers comes from the kubeadm init command if you cannot access Google servers. These images should be retagged to gcr.io ones before next steps, or the kubeadm command line would not find them:

Now the output of docker images looks like:

Also KUBE_REPO_PREFIX and other environment variables can be used to customize the prefix. I have no time to verify them.

2.4 Start the Kubernetes master

Run the init script again and it will success with further guidelines:

Run the mkdir/cp/chown command to enable kubectl usage. Then add the weave pod network. It may take some time, since images are pulled.

Now the master is finished, verify with the Ready status:

2.4 Start the Kubernetes node(slave)

A Kubernetes node only requires kube-proxy-amd64 and pause images, pull these ones:

Weave images can also been prefetched:

Join the node to our Kubernetes master by running the command line in the kubeadm init output:

3. Verify Kubernetes cluster status

Verify nodes with:

Verify internal pods with:

If the status of a pod is not Running, get the detailed info from:

If something goes wrong, and you cannot restore from it, simply reset the master/node:

4. Install Kubernetes Dashboard

By default, all user pods are allocated on Kubernetes nodes(slaves). Pull the dashboard image in advance on the node machine:

Install with alternative setup, since recommended setup is not so friendly in a development envronment:

Refer here for remote access:

Change type: ClusterIP to type: NodePort and save file. Next we need to check port on which Dashboard was exposed.

Now, you can access with: http://<master-ip>:31023/.
You can grant admin grant full admin privileges to Dashboard’s Service Account in the development environment for convenience:

5. Troubleshoting

In my office environment, errors occur and the coredns are always in CrashLoopBackOff status:

I Googled a lot, read answers from Stackoverflow and Github, reset iptables/docker/kubernetes, but still failed to solve it. There ARE unresolved issues like #60315. So I tried to switch to flannel network instead of weave. First, Kubernetes and weave need to be reset:

This time, initialize kubeadm and network with:

The flannel image can be pulled first:

Everything works. Also referred here.

Updated May 7, 2019: Kubernetes 1.13 finally add a command line switch to use an alternative repository. Simply run kubeadm with:

And verify with docker images.

Updated May 10, 2019: If using Ubuntu/Linuxmint, add repository with:

Updated June 3, 2019: flannel seems to have a close version dependency on kubernetes version. When deploying kubernetes 1.14, a specific git version should be used, according to the official document:

Updated Jan 11, 2022: Just deployed a new cluster with docker 20.10.12 & kubernetes 1.23.1.
1. kubeadm defaults to systemd, instead of cgroupfs as the container runtime cgroup driver. In docker case, edit /etc/docker/daemon.json, and restart docker service:

2. flannel script updated:

3. kubernetes dashboard script updated:

The recommended configuration enables HTTPS, and an auto-generated certificate is used. Now follow the document to create an admin user and get the login token: https://github.com/kubernetes/dashboard/blob/master/docs/user/access-control/creating-sample-user.md. Get the token with:

Mount Samba Share as a Non-root User

I used to access windows share folders directly in nautilus, or mount them like:

The problem is, they can be accessed only by root. The solution is adding a simple uid option like:

See: http://wiki.centos.org/TipsAndTricks/WindowsShares

Updated June 1, 2015:

You may encounter 121 error like:

It’s a Windows side issue, set following registry value to 3. This value tells Windows to prioritize file sharing over reducing memory usage.

Reboot (or just restart the “Server” service in services.msc). Your problem should now be solved.

See: https://boinst.wordpress.com/2012/03/20/mount-cifs-cannot-allocate-memory-mounting-windows-share/

Switching to the Linuxmint 17.1 Theme

Just upgraded to Linuxmint 17.1. Themes in the distribution were greatly improved. They’ve done a better job than Ubuntu, so I switched to the mint theme.
mint17-3

No broken visual glitch any more in eclipse. And it seems the new themes include fixes for the black background color for tooltips issue. See eclipse FAQ here.

You can compare with the previous screenshot: Configuring Ubuntu Themes in Linuxmint 17. The only fix I want to apply is to make the theme look brighter. First, go to /usr/share/themes/Mint-X-Aqua. For gtk3 applications, patch with:

For gtk2 applications, patch with: