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.

Coroutines in C++/Boost (2)

Also see my previous article: Coroutines in C++/Boost.

C++ finally has a native implementation in C++20. The principal difference between coroutines and routines is that a coroutine enables explicit suspend and resume of its progress via additional operations by preserving execution state and thus provides an enhanced control flow (maintaining the execution context).

1. Asymmetric vs Symmetric

From boost:

An asymmetric coroutine knows its invoker, using a special operation to implicitly yield control specifically to its invoker.

By contrast, all symmetric coroutines are equivalent; one symmetric coroutine may pass control to any other symmetric coroutine. Because of this, a symmetric coroutine must specify the coroutine to which it intends to yield control.

So C++20 coroutines are asymmetric ones. A coroutine only knows its parent. With the dependency, symmetric coroutines can be chained, just like a normal function calls another one. No goto semantics as with a symmetric one.

C++23 generators are also asymmetric. They are resumed repeatedly to generate a series of return values.

2. Stackless vs Stackful

Again From boost:

In contrast to a stackless coroutine, a stackful coroutine can be suspended from within a nested stackframe. Execution resumes at exactly the same point in the code where it was suspended before.

With a stackless coroutine, only the top-level routine may be suspended. Any routine called by that top-level routine may not itself suspend. This prohibits providing suspend/resume operations in routines within a general-purpose library.

Well, these two are confusing. Tutorials and Blogs have different description. To make it simple, if there is await/yield definition, it’s stackless. Then if there is something called Fiber in the language, it’s stackful.

Fibers are just like threads, they can be suspended at any stackframe. While await/yield is used as a suspend point, a stackless coroutine can only suspend at exactly that point.

A stackless coroutine shares a default stack among all the coroutines, while a stackful coroutine assigns a separate stack to each coroutine. With stackless coroutine, the code is transformed into event handlers at compile time, and driven by an event engine at run time, i.e. the scheduler of stackless coroutine. Transferring control of CPU to a stackless coroutine is merely a function call with an argument pointing to its context. Conversely, transferring CPU control to a stackful coroutine requires a context switch.

Here’s a summary of how coroutine is implemented in most popular programming languages.

Language Stackful coroutines (Fibers) Stackless coroutines (await/yield)
Java (Y2023) Virtual threads in Java 21 n/a
C n/a n/a
C++ n/a (Y2020) co_await, co_yield, co_return in C++ 20
Python n/a (Y2015) async, await/yield in Python 3.5
C# n/a (Y2012) async, await/yield in C# 5.0
Javascript n/a (Y2017) async, await/yield in ES 2017
PHP (Y2021) Fiber in PHP 8.1 n/a
Go (Y2012) Goroutine in Go 1.0
(Y2020) asynchronously preemptible in 1.14
n/a
Objective-C n/a n/a
Swift n/a (Y2021) async, await/yield in Swift 5.5
Rust n/a (Y2019) async, await in Rust 1.39

Reference

Boost.Coroutine2
Fibers under the magnifying glass
Stackful Coroutine Made Fast

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: