Hosting WordPress with Hiawatha

As of this writing, WordPress is the most ubiquitous CMS and/or blogging platform in the world. WordPress 4.1 alone has been downloaded over 23 million times. It is actively developed, frequently updated, and boasts a vast ecosystem of themes, plugins, books, services, even conferences. And though WordPress’ security track record did improve substantially in 2010 over previous years, its popularity and accessibility has nevertheless left it among the most often targeted web software out there today. If you’re going to self-host WordPress, what better webserver to accommodate this than the secure, lean and high-performance Hiawatha?

Hiawatha + WordPress

This article is fairly lengthy, and touches on just about every aspect of running your own WordPress site. If you’ve already got issues of scale, hosting, security, etc. handled and you’re just here for the Hiawatha-specific nuts & bolts of running WordPress, feel free to skip straight to it. Otherwise, brew yourself a nice cup of tea and read on.

Table of contents

  1. Why Hiawatha
  2. Hosting
  3. Security considerations
  4. Installing Hiawatha
  5. Installing WordPress dependencies
  6. Tuning PHP-FPM
  7. Tuning MySQL
  8. Installing an SSL certificate
  9. Configuring Hiawatha
  10. Installing WordPress
  11. Results

Why Hiawatha

Hiawatha webserver was written with its primary emphasis on security, which means it excels at defending against many prevalent attacks, including XSS, CSRF, SQLi, DoS, and resource exhaustion. Because of its efficient design it’s also very fast, and can handle an excessive number of simultaneous connections without difficulty—even when server resources are constrained. Despite all of these advantages, Hiawatha’s configuration syntax remains simple and direct, and it functions well with WordPress.

Drawbacks

When using Hiawatha webserver to serve up your WordPress instance, there are a few minor drawbacks compared to just going with the de facto webserver:

  1. Hiawatha offers no support for.htaccess
  2. Most WP users have never heard of Hiawatha

Unless you are a serious plugin tramp floozy trollop strumpet enthusiast, the first issue will probably never affect you. Apache and certain other webservers use .htaccess files to selectively override configured webserver behavior. While this is often convenient for web developers, it has negative consequences for security and performance. Hiawatha in general doesn’t require these invasive overrides to correctly serve a website, so for normal usage these will not be missed. The only irregularity in the context of WordPress might be that certain plugins which require .htaccess tampering may fail to function as expected. This is the exception, however, and not the rule.

On the second point, your community support options may be hampered by using anything but Apache (or Nginx to some extent). This won’t be a big deal for most people, but if you frequently rely on peer technical support, it’s worth bearing in mind.

Hosting

While the webserver does make a big difference in terms of performance, so does your choice of hosting providers. They can vary greatly in terms of quality, cost, and other factors, so it’s worth doing a bit of homework before settling on the first one that comes up in a Google search.

Server sizing

Correctly scaling your server instance for a dynamic website is a delicate balancing act to which many factors contribute. Conveying all the details of correctly sizing hardware and network resources is a topic which would require its own series of articles, but here are some general guidelines which you might find helpful:

Low traffic site

An example of a low-traffic site might be a small, personal blog which receives only a few hits daily.

  • CPU: 1 core
  • RAM: 128MB+ (at least 256MB recommended)*
  • Bandwidth: 10Mbps+

Average traffic site

This could be something like a small business website, a community hub, or a moderately popular blog.

  • CPU: 1+ cores
  • RAM: 512MB+
  • Bandwidth: 100Mbps+

Moderate traffic site

Frequently linked to from high traffic sites, particularly active user base, graphically intensive, or otherwise demanding workload such as full-time TLS.

  • CPU: 2+ cores
  • RAM: 1024MB+
  • Bandwidth: 200Mbps+

High traffic site

A very popular and/or multimedia heavy site requires a slightly more substantial hardware and bandwidth footprint.

  • CPU: 4+ cores
  • RAM: 2048MB+
  • Bandwidth: 1000Mbps+

*With some creative tuning you can get WordPress running well in as little as 128MB of RAM, but this isn’t particularly desirable for anything but bragging rights. If in doubt, go for a minimum of 512MB RAM. That will give you a bit of overhead for a few plugins and some scalability in your PHP-FPM and MySQL connection pools, which will be your primary bottlenecks in determining how many simultaneous connections your blog can support.

Choosing a provider

In the case where you don’t already have a hosting solution at hand, there are certainly many available that will get the job done. Here’s a list of attributes which are worth considering when making your selection. In no particular order:

  • The extent of your control. Some hosts allow you more authority over your host then others, the scale of which varying between bare metal (physical server hardware which you fully own and operate) on one extreme, and a shared hosting provider (like DreamHost) on the other. In my opinion, the more control you wield over server, the better off you are. In terms of picking and choosing the components of your own software stack, e.g. Hiawatha, shared hosting services are a nonstarter.
  • Price to performance ratio. In short, what are you actually getting for your money? Some providers promise astounding performance, but actually deliver only a fraction. Even so, Hiawatha itself has an extremely small resource footprint, so you are able to maximize the resources left over for necessary services like PHP and MySQL.
  • Locality and network peering. These two attributes are related, in that they both influence perceived latency from an end user’s perspective. Nearer servers tend to respond more quickly than distant ones, and better network peering arrangements often mean lower latency and better throughput even from far away.
  • Reliability and availability. The uptime of a service is not only important for reasons which should be self-evident, but as an indicator of the quality of the technology and architecture which underlies any given service. That said, SLA claims do not necessarily bear out 1-to-1 with reality, and getting a rebate on your hosting bill is little comfort if your website has any direct correlation with your livelihood.
  • Security. The security of a service is both subjective and difficult to gauge from an outsider’s perspective. Even so, a company’s security track record is a pretty decent indicator. Similarly I’d personally consider having user-friendly services such as CPanel or Virtuozzo forced upon you, especially if you are unable block or disable such an interface, to be an untenable proposition.
  • Value-added features. Things like regular snapshots or backup services, live VM migrations, in place re-scaling, or API availability might be of supreme importance or completely inconsequential depending on your needs. If you need one particular feature or other, make sure you’re aware of what a hosting provider offers and what it doesn’t before coming to a decision.
  • Standards and compliance. If you’re an individual blogger, issues of compliance aren’t much of a concern at all. But if your WP instance will be for corporate use or any otherwise subject to a governing body, make sure your host of choice meets the requirements of your particular circumstances.

If you find the sheer number of options out there a bit daunting, RamNode has been my go-to provider for the last few years. They’re fast, reliable, and extremely cost effective. They’ve also got a great carrier blend, along with physical presences across the continental US and in Europe.

Security considerations

Having Hiawatha run your WordPress site isn’t enough to protect your server instance alone. You’ll still need to safeguard the rest of the stack in order to prevent an attacker from gaining access through some other mechanism.

  • Secure your network stack. Defend your server with a strong firewall policy, so that malformed packets are dropped by default and only necessary services are exposed. The quickest and most effective way to do this is typically with a firewall wrapper script. If you’re running Linux, a few good examples of IPTables wrappers are UFW, Shorewall, CSF, AIF or the Hiawatha author’s own Firetable.
  • Harden OpenSSH. SSH can be a secure way to access your host, but it isn’t magical. You still need to ensure that you’re following best-practice guidelines to get a first-rate result.
  • Keep your system up to date. While Hiawatha can prevent so many types of attacks against your webstack that web hosting becomes a bit “set and forget,” don’t be lulled into a false sense of security. It’s still entirely prudent to keep WordPress current and regularly update your host with system-level security patches.
  • Install only trustworthy WordPress plugins. Most WordPress vulnerabilities are actually due to flaws in plugins rather than WordPress itself. Be very cautious when selecting which plugins you’ll install and practice diligence in keeping them up to date. If in doubt about a plugin, don’t install it.
  • Take precautions against side-channel attacks. Even if your host is perfectly configured, there may be other ways for an adversary to gain unauthorized access. For instance, what about the management dashboard for your hosting service? Does it have a weak password? How about your DNS provider account? These are the types of things most people don’t think about, but are every bit as relevant to safeguarding both your data and your users’ privacy.

Quick RamNode primer script

Want to get your server hardened and ready for Hiawatha in the quickest way possible? If you’re planning to use RamNode and Debian anyway, you might consider using my RamNode provisioning script to get you started off on the right foot.

NOTE: This script has been further streamlined and updated to run on Debian 8.x (Jessie-LTS) by default, thus some of the information below is outdated. See: RamNode Debian primer script

What it does

  • Prepares a RamNode-based OpenVZ VPS running Debian 7 64-bit to be a secure, high-performance server in under 3 minutes.
  • Completely cleans out a bunch of unnecessary packages.
  • Updates the entire system to the latest patch level.
  • Adds repositories for Hiawatha, DotDeb (backported PHP, Redis, etc.), and imports their respective GPG public keys.
  • Installs some common and useful administrative tools.
  • Tunes the network stack for optimal security and performance.
  • Applies a more secure initial configuration policy for OpenSSH.
  • Installs and configures Fail2Ban to insulate OpenSSH against brute force and DoS attacks.
  • Sets up UFW firewall with a default-deny policy.
  • Randomly chooses a port for SSH to listen on and automatically adds a corresponding pinhole in the firewall policy.
  • Allows you to configure your main admin user account and adds it to the required system groups.
  • Locks the built-in root account from direct logins.
  • When the script has finished, it helpfully gives you an SSH stanza to paste into your ~/.ssh/config file.

Requirements

  • An OpenVZ container on RamNode.
  • The VPS should be running an unaltered, 64-bit installation of Debian 7 “Wheezy”.*
  • An up-to-date SSH client to securely manage the VPS.

*If your host has a different OS configuration but you’d like to use my script, login to the SolusVM control panel, click “Manage”, select “Debian 7.0 64-bit” from the list, then click “Reinstall”. This will delete all data on the host, so if there’s anything you want to keep, make sure to get a backup first!

Usage

  1. SSH to your new VPS. For example, if the IP address is 192.0.2.42:
  2. ssh -C -l root 192.0.2.42

  3. Download and run the RamNode primer script:
  4. cd /tmp &&\
    wget https://files.tuxhelp.org/scripts/ramnode-bootstrap.sh &&\
    sh ramnode-bootstrap.sh

  5. One of the packages which will be upgraded is OpenSSH-server, which will query as to whether you want to disable SSH password authentication for root. Don’t worry, your answer here doesn’t matter; the script will overwrite the relevant configuration file afterwards anyway.
  6. When prompted to add your primary administrative user, choose a unique username. As an additional precaution, avoid generic names like “admin”, “user”, “support”, “web”, etc.
  7. After the script is finished running, it will produce an SSH stanza for your new host. Make sure to record this information before proceeding. It contains your username, the host’s name and IP, and most importantly, the random SSH port that was chosen during the provisioning process.
  8. Once you’ve copied your SSH stanza to ~/.ssh/config, allow the server to reboot.
  9. After 10 seconds or so, you can SSH to your new VPS, e.g. “myhost”:
  10. ssh myhost

  11. If your server instance is closer to Norway than to the US, edit your /etc/apt/sources.list as follows to use the Norwegian Hiawatha mirror:
  12. #deb http://mirror.tuxhelp.org/debian/ squeeze main
    deb http://apt.sparkz.no/debian/ squeeze main

  13. Update your package list and install Hiawatha:
  14. sudo aptitude update && sudo aptitude install hiawatha

  15. Add a pinhole in UFW for Hiawatha. Assuming you will be utilizing both HTTP and HTTPS:
  16. sudo ufw insert 1 allow 80,443/tcp

  17. Optional: copy your SSH public key(s) to the system, and disable PasswordAuthentication in /etc/ssh/sshd_config:
  18. # Change to no to disable tunnelled clear text passwords
    PasswordAuthentication no

    NOTE: OpenVZ and other containers prevent OpenSSH-server from restarting gracefully, so your changes will require a reboot before they go into effect. It’s a very good idea to test that your keys are working before doing so, of course, so that you don’t accidentally lock yourself out.

Installing Hiawatha

If you haven’t already installed Hiawatha, it may be compiled from source, from a pre-compiled binary package, or through a software repository. The latter is generally preferable, as it makes it trivial to keep Hiawatha up to date along with the rest of your system.

Most of the remainder of this howto will assume a tack which is Debian-centric, since that is the flavor of *nix I personally prefer and work with most frequently. It’s also the distribution I provide packages for, so I’m certain it’s well supported for Hiawatha. Likewise I’ll refer to APC as an OP cache for PHP, as Debian stable ships with PHP 5.4, and MariaDB simply because I find the packaging more desirable and its cautious pace less problematic than Percona. If you’re not running this particular stack, the principles should be generic enough that you can easily adapt them to your own situation.

Adding a Debian repository

Should you be running Debian Linux (or certain derivatives thereof), there two repositories which track very closely with the Hiawatha release process. One is located in Kristiansand, Norway, and the other is in Seattle, Washington, USA. While both should work from just about anywhere, the mirror which is geographically closest to you will almost certainly be faster. Instructions for using these repositories are included in the index of each respective mirror:

Once your repository of choice is setup, you can simply install Hiawatha just like any other Debian package:

sudo aptitude install hiawatha

– or –

sudo apt-get install hiawatha

Installing pre-compiled binaries

There are many binaries available for Hiawatha webserver, which are listed on the Hiawatha download page. It’s important to use packages that are consistently maintained however, and some 3rd-party packagers / distributions tend to lag several releases behind. If that’s the case for your distro of choice, you may be better off building your own packages.

Compiling from source

In a situation where there is no current package available for your distribution, you’re running an uncommon architecture, or if you simply prefer to build your own packages, you can always compile from source. Hiawatha has minimal dependencies and requirements, so this is on the whole a painless process which is left as an exercise for the reader. See the official howto on compiling and installing Hiawatha.

Installing WordPress dependencies

Aside from a webserver, WordPress has a few essential requirements in order to function:

  • PHP 5.2.4 or later (recommended: PHP 5.4.x or greater)
  • PHP-FPM (PHP Fast Process Manager)
  • Some additional PHP modules:
    • php5-curl
    • php5-gd
    • php5-json*
    • php5-mcrypt
    • php5-mysql
  • MySQL 5.0.15 or later (recommended: MySQL 5.5.x or greater)

*php5-json is often included either within the main PHP binary or as a dependency thereof, so you usually won’t have to install it implicitly.

Optional but recommended dependencies

  • php5-mysqlndMySQL Native Driver is a more efficient way for PHP to communicate with MySQL than the standard php5-mysql
  • An OPcache for PHP
    • php5-opcacheOPcache improves PHP performance by storing precompiled script bytecode in shared memory (bundled with PHP 5.5.0 and later)
    • php5-apcAlternative PHP Cache will make your PHP application faster and less CPU-intensive at the expense of some RAM
    • php5-xcacheXCache is another open-source opcode cacher, similar in form and function to APC
  • An advanced MySQL fork
    • percona-serverPercona Server is an enhanced, drop-in replacement for MySQL
    • mariadb-serverMariaDB is Percona’s more FOSS-oriented cousin

Example: installing dependencies on Debian

If I was to install these dependencies on a Debian-based system, it’d look something like this:

sudo aptitude install mariadb-server-5.5 php5 php5-gd \
php5-mcrypt php5-curl php5-fpm php-apc php5-mysqlnd


When MariaDB (or MySQL, or Percona) is first installed, it will request a new root password for database administration.
MariaDB setting root passwordTo avoid complications, I recommend an 8-16 character password containing only alphanumeric characters (though both upper and lower cases are fine).

Tuning PHP-FPM

Optimizing PHP-FPM is a bit like tuning Apache Prefork, in that they both create forks to handle each client connection. However FPM has the advantage that each process is doing much less work, handling PHP and nothing more, thus utilizes fewer resources and has a more predictable memory footprint than Apache. Hiawatha handles the actual client portion of the transaction, including serving static assets directly, which is faster, more secure, and scales far better.

How PHP-FPM should be tuned depends heavily on your server’s resources—especially RAM, but also CPU performance and core count—as well as how much memory your applications require, their computational demands, etc. Therefore there is no ‘one size fits all’ optimal configuration.

PHP-FPM is capable of running multiple concurrent instances, known as “pools”, or only a single one. By convention these pools are ordinarily stored in FPM’s pool.d directory, e.g. /etc/php5/fpm/pool.d/, and the default configuration is called www.conf. If you’ll only be utilizing one FPM pool, you can simply edit /etc/php5/fpm/pool.d/www.conf in place.

While PHP-FPM’s out-of-the-box settings are pretty sane, there are a few key configuration settings you’ll want to look into:

  • user / group: Unless you have a reason not to, these should be configured to run as the same user as Hiawatha (www-data by default on most systems).
  • user = www-data
    group = www-data
  • listen: Where PHP-FPM listens for connections, the options being either a UNIX socket or a TCP port. A socket is generally faster and more efficient, though FPM can also listen on a port if you’d prefer. Just make sure each port or socket is unique if you happen to run multiple pools on the same host.
  • listen = /var/run/php5-fpm.sock
  • listen.owner / listen.group: who should own the UNIX socket, if sockets are used and not ports. This should be the same as your Hiawatha webserver, e.g. www-data.
  • listen.owner = www-data
    listen.group = www-data
  • listen.mode: The permissions for the UNIX socket, if sockets are used. For security these should usually be readable and writeable for the socket owner/group, and not accessible at all by other users.
  • listen.mode = 0660
  • pm: Process manager; how the process manager will control the number of child processes. This is an important setting in terms of both performance and resource consumption. Possible values are ‘static‘, ‘dynamic‘, and ‘ondemand‘. The ‘static‘ method may be preferable on systems with a lot of RAM for efficiency reasons. The ‘ondemand‘ option uses the least amount of resources at idle, but incurs the most overhead and latency of the three since child processes must be spawned for each request. Most people will want to use ‘dynamic‘ here.
  • pm = dynamic
  • pm.max_children: Sets the limit on the number of simultaneous requests that will be served. This number will play a pivotal role in how many concurrent users your WordPress instance can support. Unfortunately each child incurs its own memory footprint, so this value must necessarily be adjusted to fit within the constraints of your host’s available RAM. This footprint can also change dramatically if your WP instance will be using many plugins, or any plugins which use excessive amounts of memory. For a server with only 256MB RAM, a single WP instance and few to no WP plugins will be installed, 32 is probably a reasonable starting point.
  • pm.max_children = 32
  • pm.start_servers: The number of child processes created on startup when the process manager is set to dynamic. A good rule of thumb here is to use a multiple of your host’s CPU core count. On a host with minimal RAM, this might be 1:1 with the core count, or at least 2 if you’ve only got a single CPU core.
  • pm.start_servers = 2
  • pm.min_spare_servers: The target minimum number of idle server processes when in dynamic mode. In other words, how many spare processes should be ready for work. Standby servers can greatly reduce latency and help you handle traffic spikes more gracefully, as having an idle process already running is much more efficient than spawning a new one when it’s required (as is the case with the ondemand mode). Idle processes do consume memory of course, so this number shouldn’t be arbitrarily high either. As with pm.start_servers, setting this to a multiple of your CPU core count is a good idea. However, it should set be lower than the value you’ve set for pm.start_servers.
  • pm.min_spare_servers = 1
  • pm.max_spare_servers: The desired maximum number of idle server processes in dynamic mode. The simplest way to calculate the appropriate setting for this option is to add the values from the pm.start_servers and pm.min_spare_servers settings together and use the sum.
  • pm.max_spare_servers = 3
  • pm.max_requests: How many requests each child process should execute before respawning. This is primarily to counteract the affects of memory leaks in PHP code. If you’ll be running lots of WP plugins or PHP libraries of dubious technical quality, setting this value relatively low (200-500) can help keep your FPM memory consumption reasonable. Otherwise, you can set it higher (1000-2000) or disable it entirely (0) for endless request processing.
  • pm.max_requests = 1000

Once you’ve tuned to your satisfaction, restart PHP-FPM to put your changes into effect:

sudo /etc/init.d/php5-fpm restart

Optimizing PHP

On most systems, PHP is fortunately configured fairly well by default these days, but there are some settings which might need to be adjusted for security and performance. When running PHP-FPM, your main PHP configuration file (php.ini) will usually be within PHP-FPM’s configuration path (/etc/php5/fpm/php.ini), so that’s where you’ll want to focus your attention.

  • disable_functions: Allows you to disable certain functions for security reasons. Here are some functions which should definitely be safe to exclude:
  • disable_functions = pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,
  • expose_php: Whether PHP may expose the fact that it is installed on the server by sending a custom header. This vanity header eats a few bytes per connection and leaks your exact version of PHP to the world. Therefore, it should be disabled.
  • expose_php = Off
  • max_execution_time: Maximum execution time of each script, in seconds. 30 should be enough for WP on any reasonably fast system.
  • max_execution_time = 30
  • max_input_time: Maximum amount of time each script may spend parsing request data. The PHP default value of 60 is usually fine here.
  • max_input_time = 60
  • memory_limit: The maximum amount of memory a script may consume. For a single WP instance with few or no plugins, 32MB is probably enough. If you add more plugins or run WordPress as multi-site instance however, this limit will most likely need to be increased substantially. 128MB is a decent ceiling for blogs with a lot of plugins, but I can’t imagine any reasonable WP installation requiring more than 256MB.
  • memory_limit = 32M
  • display_errors: This directive controls whether or not and where PHP will output errors, notices and warnings. In production, this option should always be disabled.
  • display_errors = Off
  • post_max_size: Maximum size of POST data that PHP will accept. Along with upload_max_filesize, this determines the largest file you’re able to upload to WP through the administrative UI. Since most media assets are well under 1MB, 2MB should be a safe limit.
  • post_max_size = 2M
  • cgi.fix_pathinfo: Provides real PATH_INFO/PATH_TRANSLATED support for CGI. When used with Hiawatha this is unwanted behavior, so it should be disabled.
  • cgi.fix_pathinfo = 0
  • file_uploads: Whether to allow HTTP file uploads. If you want to be able to upload media to WP, you’re going to need this enabled.
  • file_uploads = On
  • upload_max_filesize: Maximum allowed size for uploaded files. Along with post_max_size, this determines the largest file you’re able to upload to WP through the administrative UI. Since most media assets are well under 1MB, 2MB should be a safe limit.
  • upload_max_filesize = 2M
  • allow_url_include: Whether to allow include/require to open URLs (like http:// or ftp://) as files. This should be disabled.
  • allow_url_include = Off

Optional: tuning APC

Even without configuration, APC will greatly outperform a raw PHP stack. However, a little tuning will improve these results even further. For illustration, here are some general performance results* with raw PHP, APC out of the box, and APC after being tuned:

PHP Module Mean Latency Requests/second
N/A (raw PHP) 852ms 150
APC untuned 169ms 757
APC tuned 124ms 1,030

*Tested across 10 network hops, ~10ms latency. Host server hardware: E5-2630L CPU, 1 core, 256MB RAM, SSDs in RAID-10.

On Debian, with PHP-FPM, APC’s configuration file is ‘/etc/php5/fpm/conf.d/20-apc.ini‘, but may be located elsewhere depending on your own system flavor. Here’s a model config, tuned for a system with only a single WordPress instance and 256MB of system memory:

[APC]
extension=apc.so
apc.enabled=1
apc.shm_segments=1

;32M per WordPress install
apc.shm_size=32M

;Relative to the number of cached files (you may need to watch your stats for a day or two to find out a good number)
apc.num_files_hint=7000

;Relative to the size of WordPress
apc.user_entries_hint=4096

;The number of seconds a cache entry is allowed to idle in a slot before APC dumps the cache
apc.ttl=0
apc.user_ttl=0
apc.gc_ttl=0

;Setting this to 0 will give you the best performance, as APC will
;not have to check the IO for changes. However, you must clear 
;the APC cache to recompile already cached files. If you are still
;developing, updating your site daily in WP-ADMIN, and running W3TC
;set this to 1
apc.stat=1

;This MUST be 0, WP can have errors otherwise!
apc.include_once_override=0

;Only set to 1 while debugging
apc.enable_cli=0

;Allow 2 seconds after a file is created before it is cached to prevent users from seeing half-written/weird pages
apc.file_update_protection=2

;Leave at 2M or lower. WordPress does't have any file sizes close to 2M
apc.max_file_size=2M

;Ignore files
apc.filters = "/var/www/apc.php"

apc.cache_by_default=1
apc.use_request_time=1
apc.slam_defense=0
apc.mmap_file_mask=/apc.shm.XXXXXX
apc.stat_ctime=0
apc.canonicalize=1
apc.write_lock=1
apc.report_autofilter=0
apc.rfc1867=0
apc.rfc1867_prefix =upload_
apc.rfc1867_name=APC_UPLOAD_PROGRESS
apc.rfc1867_freq=0
apc.rfc1867_ttl=3600
apc.lazy_classes=0
apc.lazy_functions=0

If your host has more memory than my testbed from which the above configuration is derived, in addition to increasing apc.shm_size, you’ll probably want to also add a larger strings buffer to further increase performance:

apc.shm_strings_buffer=8M

This value will actually serve to reduce memory pressure and fragmentation within APC overall, but you have to actually have the memory in order to spend it. Use your best judgement.

After making your changes, you’ll have to restart PHP-FPM to put them info effect:

/etc/init.d/php5-fpm restart

This is also a good way to defragment APC’s cache after you’ve done something major to your CGI, such as upgrading WordPress to a more recent version.

For more on tuning APC for WordPress, I refer you to The Perfect APC Configuration by Greg Rickaby and, for more general info, to the APC configuration manual.

Tuning MySQL

If you’re reading this section you, like myself, are probably not a career database administrator. Thankfully Percona has provided a MySQL configuration wizard that can serve as a very good starting point for your MySQL configuration.

Most questions in the wizard will be directly related to your environment, e.g. “How many CPU cores”, “How much RAM”, etc. Others may be a little less obvious.

  • The typical workload should be defined as “OLTP” (online transaction processing).
  • Percona wizard workload selection

  • WordPress’ table count will more often than not be under a hundred, but may grow larger depending on various factors. It’s generally safe to choose “less than 1000” here.
  • Percona wizard table count selection

  • Your preferred storage engine should be “InnoDB”, especially if you’re using Percona or MariaDB. These actually utilize the XtraDB storage engine, an updated and high performance version of InnoDB, which is utilized transparently in place of the more traditional InnoDB.
  • Percona wizard storage engine selection

Once you have your finished configuration, first stop MySQL:

sudo /etc/init.d/mysql stop

Next, make a backup of /etc/mysql/my.cnf:

sudo cp /etc/mysql/my.cnf \
/etc/mysql/my.cnf.bak

Then use the output from the Percona configuration wizard to overwrite the existing /etc/mysql/my.cnf:

sudo vim /etc/mysql/my.cnf

Feel free to replace ‘vim’ with your editor of choice.

On Debian, and perhaps other platforms, the default location for mysqld.sock and mysqld.pid don’t match up with the defaults from the Percona tuning wizard. You may have to change them to reflect the correct location, e.g.:

socket   = /var/run/mysqld/mysqld.sock
pid-file = /var/run/mysqld/mysqld.pid

If your innodb-log-file-size has changed, you’ll also need to delete any existing binlog files before starting MySQL:

sudo rm -f /var/lib/mysql/ib_logfile*

Now you can restart MySQL with your new configuration:

sudo /etc/init.d/mysql start

New binlog files of the correct size will automatically be created as part of the initialization process.

Optional: install MySQL Tuning Primer script

There’s a helpful script which gives a quick overview of your current MySQL setup, and provides suggestions if things look askew. Unfortunately it hasn’t been updated since 2011, so I did a little patching and made the updated version available here. To install the tuning primer script:

sudo wget https://files.tuxhelp.org/scripts/tuning-primer.sh \
-O /usr/local/bin/tuning-primer.sh && \
sudo chmod 755 /usr/local/bin/tuning-primer.sh

Now you can run it:

tuning-primer.sh


It can also create a handy MySQL socket for you in your home directory, so that you don’t need to use your password to run tuning-primer.sh or login to the MySQL console. If you’re logging in with MySQL’s built-in root user account, I recommend likewise placing the privileged socket in the system’s root home directory (/root) to keep it away from prying eyes. To do so, first gain a root shell:

sudo su -

Then run tuning-primer.sh:

tuning-primer.sh

tuning-primer.sh socket creation dialogWhen asked for your account information, enter root for the user name and the password you set earlier.

Now you can run tuning-primer.sh and analyze your running MySQL configuration. Of particular interest should be the “Memory Usage” section. The script will tabulate all of the buffers, both global and per-thread, and provide you with the sum.
tuning-primer.sh memory usage reportIf the max memory limit is too high, you’re going to need to do some tuning to get that ceiling down. Some of the key tuneables to investigate are as follows:

  • innodb_buffer_pool_size: For a single WordPress instance 8MB may be a good enough starting point, though you may need to make it larger if your content outgrows it. That said, the more of your data set you can fit in RAM the better.
  • max_connections: Each connection requires a multiple of your per-thread buffers, so will likely be one of the prime consumers of memory, and additionally, one of the primary bottlenecks in how many simultaneous clients you can support. A good rule of thumb is to set this to the same value as pm.max_children in your PHP-FPM pool.
  • key-buffer-size: If you’re using InnoDB/XtraDB rather than the outmoded MyISAM, this value can be quite low. 1MB is probably fine if there are no active MyISAM databases.
  • tmp-table-size: WordPress normally doesn’t have a particularly large SQL footprint, so the temp table size doesn’t need to be too big either. 1MB might be a good starting point, making adjustments later if too many temp tables end up on disk.
  • max-heap-table-size: As above, 1-2MB is probably fine for a single WP instance, but keep an eye on your server stats and tune if needed.
  • log-queries-not-using-indexes: This doesn’t have much direct impact on memory, but it does incur some overhead. If you’re only utilizing WordPress and not doing development that requires restructuring your SQL schema, it’s not relevant data for you and may be disabled.
  • slow-query-log: This could potentially be useful, even as only a consumer of WordPress, as an indicator if whether your SQL queries are returning in a timely manner. However, it does incur some overhead and should be disabled if you’ll never actually check the statistics it’s gathering.

After re-tuning, run tuning-primer.sh again and verify your results.
tuning-primer.sh memory usage report after tuning
If MySQL’s memory footprint looks more in-line with your available resources, you’re good to proceed. You can always tweak more later as your site experiences real traffic. Otherwise, continue tuning your configuration until you achieve the desired affect.

Installing an SSL certificate

Even if your blog is for personal use and you don’t plan to offer HTTPS full time, you really should at least encrypt access to the WordPress admin UI in order to keep your admin credentials secure. This will require an SSL certificate.

A signed & validated certificate

SSL certs are really cheap these days, commonly under $10/yr for a domain-validated cert. I most often prefer NameCheap to register domains and buy certs for personal use (and I likewise avoid GoDaddy at all costs for various reasons that I won’t go into here). All you need to do is generate a certificate signing request, or ‘CSR’, and provide it when you request your new certificate:

openssl req -out blog_example_tld.csr -new -sha512 -newkey rsa:4096 -nodes -keyout blog_example_tld.key

Answer the simple questions which follow, and in the end you’ll wind up with a CSR and a private key.

Generating a 4096 bit RSA private key
...............++
.++
writing new private key to 'example_tld.key'
-----
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:US
State or Province Name (full name) [Some-State]:California
Locality Name (eg, city) []:San Francisco
Organization Name (eg, company) [Internet Widgits Pty Ltd]:My Awesome Company
Organizational Unit Name (eg, section) []:.
Common Name (e.g. server FQDN or YOUR name) []:blog.example.tld
Email Address []:me@example.tld

Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
An optional company name []:My Awesome Company

NOTE: if you set a challenge password when generating your key, this will be required in order to start your webserver… every time. For that reason, I don’t recommend setting a password for certs in this particular situation. However, it’s extremely important that you take every precaution to keep your key safe and private; it should never be transmitted over the Internet insecurely, sent in email, stored someplace where others might be able to access it, or otherwise handled carelessly.

Once your certificate has been issued, it will commonly come in several parts. Together, they form a chain, which will need to be bundled along with your key, before referencing it in Hiawatha’s configuration. To make this bundle correctly, the order of the certificates is important; you’re starting with your own host certificate first and working your way through the intermediates (if any) towards the root CA.* Here’s an illustration of how I’d concatenate my certificate chain together if I’d bought a Comodo PositiveSSL certificate:

cat blog_example_tld.crt COMODORSADomainValidationSecureServerCA.crt COMODORSAAddTrustCA.crt > blog_example_tld.bundle

This cert will need to be placed someplace secure on your server’s filesystem, along with the key you generated earlier.

First, I’ll make a directory where I will store my own local certs:

sudo mkdir -p /etc/ssl/localcerts/

And change the permissions so that only root is allowed to traverse or write into it:

sudo chown root:root /etc/ssl/localcerts/
sudo chmod 0750 /etc/ssl/localcerts/

Then I can prep a blank file for my new cert bundle with the correct permissions already in place:

sudo touch /etc/ssl/localcerts/blog_example_tld.pem
sudo chmod 640 /etc/ssl/localcerts/blog_example_tld.pem

Now I can populate it with my certificate chain and the private key from the CSR creation process:

sudo cat blog_example_tld.bundle blog_example_tld.key > /etc/ssl/localcerts/blog_example_tld.pem

*You never need to include the root CA in your cert bundle. Either your client already has the root in their list of trusted CAs, or they don’t. In the former case, you’re sending the cert redundantly, since they already have it. In the latter, they won’t trust it regardless of whether you send it or not. Ergo, sending the root cert is completely pointless.

A self-signed certificate

If you really don’t want to spend your hard-earned latte money on a proper cert, you should at the very least make a self-signed one (also colloquially known as a “snake oil cert”, since like the tonics and potions of bygone days, they’re basically B.S.). This will at least give you cryptographic functionality, even if it is trivial to spoof without a proper chain of trust.

NOTE: This section can be considered obsolete, as Let’s Encrypt has become a viable solution since the time of this writing. See: Let’s Encrypt with Hiawatha

First, I’ll make a directory where I’ll store my own local certs:

sudo mkdir -p /etc/ssl/localcerts/

And change the permissions so that only root is allowed to traverse or write into it:

sudo chown root:root /etc/ssl/localcerts/
sudo chmod 0750 /etc/ssl/localcerts/

Then I can prep a blank file for my new cert bundle with the correct permissions already in place:

sudo touch /etc/ssl/localcerts/blog_example_tld.pem
sudo chmod 640 /etc/ssl/localcerts/blog_example_tld.pem

Now I can populate it with a self-signed certificate and private key:

sudo openssl req -x509 -nodes -days 365 -newkey rsa:4096 -sha512 -keyout /etc/ssl/localcerts/blog_example_tld.key -out /etc/ssl/localcerts/blog_example_tld.crt
sudo cat /etc/ssl/localcerts/blog_example_tld.crt /etc/ssl/localcerts/blog_example_tld.key > /etc/ssl/localcerts/blog_example_tld.pem

Configuring Hiawatha

Hiawatha has a fairly straightforward configuration syntax which should be appealing for technical veterans and novices alike. It can be handled as a single, monolithic configuration file, or as a series of smaller configuration snippets in one or more includes directories depending on your own preferences.

For the sake of this tutorial, and because this particular case is relatively simple, I’ll use the single config file method then break it down into individual components and describe what each one does. Some settings are tuned with values appropriate to a server which is very resource constrained with only 256MB of RAM in total, so may be far too conservative if your server instance is much larger. You should use your best judgement when writing your own config. Additionally, this sample configuration is not all-inclusive of the options available in Hiawatha. For that, you’ll need to consult the Hiawatha manual.

Here’s an example /etc/hiawatha/hiawatha.conf:

hiawatha.conf

# Hiawatha main configuration file
#
# This is a conservative hiawatha.conf example for use with WordPress.
#

# VARIABLES
# With 'set', you can declare a variable. Make sure the name of the
# variable doesn't conflict with any of the configuration options. The 
# variables are case-sensitive and cannot be re-declared.
#
set LOCALHOST = 127.0.0.0/8
set MyIPv4 = 192.0.2.42
set MyIPv6 = fde4:8dba:82e1:ffff::42


# GENERAL SETTINGS
#
ServerString = Hiawatha
ServerId = www-data
ConnectionsTotal = 4096
ConnectionsPerIP = 32
SystemLogfile = /var/log/hiawatha/system.log
GarbageLogfile = /var/log/hiawatha/garbage.log
ThreadKillRate = 10
CacheSize = 8
CacheMaxFilesize = 512
MaxUrlLength = 1000
MinSSLversion = TLS1.0
DHsize = 4096
SocketSendTimeout = 30
LogfileMask = deny LOCALHOST, deny MyIPv4, deny MyIPv6
RequestLimitMask = deny LOCALHOST, deny MyIPv4, deny MyIPv6


# BINDING SETTINGS
# A binding is where a client can connect to.
#
Binding {
        Port = 80
        MaxKeepAlive = 100
        TimeForRequest = 5,15
        MaxRequestSize = 2000
        MaxUploadSize = 2
}

Binding {
        Port = 443
        MaxKeepAlive = 100
        TimeForRequest = 5,15
        SSLcertFile = /etc/ssl/localcerts/blog_example_tld.pem
        MaxRequestSize = 2000
        MaxUploadSize = 2
}

Binding {
        Port = 80
        Interface = MyIPv6
        MaxKeepAlive = 100
        TimeForRequest = 5,15
        MaxRequestSize = 2000
        MaxUploadSize = 2
}

Binding {
        Port = 443
        Interface = MyIPv6
        MaxKeepAlive = 100
        TimeForRequest = 5,15
        SSLcertFile = /etc/ssl/localcerts/blog_example_tld.pem
        MaxRequestSize = 2000
        MaxUploadSize = 2
}

# BANNING SETTINGS
# Deny service to clients who misbehave.
#
BanOnGarbage = 300
BanOnInvalidURL = 60
BanOnMaxPerIP = 15
BanOnMaxReqSize = 300
BanOnWrongPassword = 6:900
BanOnSQLi = 3600
KickOnBan = yes
RebanDuringBan = yes
BanlistMask = deny LOCALHOST, deny MyIPv4, deny MyIPv6
ChallengeClient = 768, javascript, 15


# COMMON GATEWAY INTERFACE (CGI) SETTINGS
# These settings can be used to run CGI applications.
#
FastCGIserver {
        FastCGIid = PHP5
        ConnectTo = /var/run/php5-fpm.sock
        Extension = php
}


# URL TOOLKIT
# This URL toolkit rule was made for the Banshee PHP framework, which
# can be downloaded from http://www.hiawatha-webserver.org/banshee
#
#UrlToolkit {
#	ToolkitID = banshee
#	RequestURI isfile Return
#	Match ^/(css|files|images|js|slimstat)($|/) Return
#	Match ^/(favicon.ico|robots.txt|sitemap.xml)$ Return
#	Match .*\?(.*) Rewrite /index.php?$1
#	Match .* Rewrite /index.php
#}
UrlToolkit {
    ToolkitID = cache-control
    Match ^/.*\.(css|eot|gif|htm|html|ico|jpeg|jpg|js|otf|pdf|png|ps|psd|svg|swf|ttf|txt|woff|woff2)(\?v=.*|\?ver=.*)?(/|$) Expire 1 weeks
}

UrlToolkit {
    ToolkitID = wordpress
    RequestURI exists Return
    Match .*\?(.*) Rewrite /index.php?$1
    Match .* Rewrite /index.php
}

UrlToolkit {
    ToolkitID = secure-wp
    UseSSL Skip 2
    Match ^/wp-login.php(.*) Redirect https://blog.example.tld/wp-login.php$1
    Match /wp-admin/$ Redirect https://blog.example.tld/wp-admin/$1
}


# DEFAULT WEBSITE
# It is wise to use your IP address as the hostname of the default website
# and give it a blank webpage. By doing so, automated webscanners won't find
# your possible vulnerable website.
#
Hostname = 127.0.0.1
WebsiteRoot = /var/www/hiawatha
StartFile = index.html
AccessLogfile = /var/log/hiawatha/access.log
ErrorLogfile = /var/log/hiawatha/error.log
#ErrorHandler = 404:/error.cgi


# VIRTUAL HOSTS
# Use a VirtualHost section to declare the websites you want to host.
#
VirtualHost {
        Hostname = blog.example.tld, *.blog.example.tld
        WebsiteRoot = /srv/www/vhosts/blog_example_tld
        StartFile = index.php
        TimeForCGI = 60
        UseFastCGI = PHP5
        CustomHeader = X-Frame-Options: sameorigin
        CustomHeader = Vary: Accept-Encoding
        RandomHeader = 64
        UseToolkit = secure-wp, wordpress, cache-control
        EnforceFirstHostname = yes
        PreventXSS = yes
        PreventCSRF = yes
        PreventSQLi = yes
}


# DIRECTORY SETTINGS
# You can specify some settings per directory.
#
Directory {
        Path = /srv/www/vhosts/blog_example_tld/wp-content/themes
        UseGZfile = yes
}

Raw text version here.

variables

# VARIABLES
# With 'set', you can declare a variable. Make sure the name of the
# variable doesn't conflict with any of the configuration options. The 
# variables are case-sensitive and cannot be re-declared.
#
set LOCALHOST = 127.0.0.0/8
set MyIPv4 = 192.0.2.42
set MyIPv6 = fde4:8dba:82e1:ffff::42

Variables are completely optional to your Hiawatha configuration, but are handy in that they can make your config both more readable and easier to edit going forward. This is due to the fact that if you need to change something that's defined as a global variable, you only need to change the value of the variable, not edit every place in the configuration where the value exists. If you don't find global variables helpful, feel free to leave them out of your own configuration and simply hard-code every value.

general settings

# GENERAL SETTINGS
#
ServerString = Hiawatha
ServerId = www-data
ConnectionsTotal = 4096
ConnectionsPerIP = 32
SystemLogfile = /var/log/hiawatha/system.log
GarbageLogfile = /var/log/hiawatha/garbage.log
ThreadKillRate = 10
CacheSize = 8
CacheMaxFilesize = 512
MaxUrlLength = 1000
MinSSLversion = TLS1.0
DHsize = 4096
SocketSendTimeout = 30
LogfileMask = deny LOCALHOST, deny MyIPv4, deny MyIPv6
RequestLimitMask = deny LOCALHOST, deny MyIPv4, deny MyIPv6
  • ServerString: What Hiawatha should send as the "server" string. This is purely superfluous, but personally I like to send "Hiawatha", sans version (which is the default, e.g. "Server: Hiawatha v9.12") for statistical reasons. In reality, you can send anything as your server string... "Server: I am Sparticus."
  • ServerId: Which UNIX user the Hiawatha server process should run as. By default on many systems, this will be www-data, but you can run Hiawatha as any valid system user. It should go without saying that you should never, ever run a webserver as a privileged user such as root. Further, for security reasons I'd strongly recommend making sure that the user account which owns the Hiawatha process is non-interactive, meaning it shouldn't have either a shell or valid login credentials.
  • sudo usermod -L -s /bin/false www-data
  • ConnectionsTotal: How many simultaneous connections Hiawatha should allow. This includes both direct static content delivery and inter-process connections, such as CGI or reverse-proxied connections. Hiawatha itself requires very little in terms of resources, so even on small systems you can set this value high. On more robust server hardware, you can set this cap sufficiently high that bandwidth will most likely be a bottleneck long before Hiawatha is.
  • ConnectionsPerIP: How many connections an individual client IP can utilize simultaneously. This is an anti-DoS countermeasure. For a typical site, a value in the neighborhood of 32-64 should be plenty. If your site receives an unusual amount of legitimate connections from shared IPs such as public proxy servers, you might need to set this higher. Supposing that for some reason you really wish to disable this defensive capability, you can set the value to match ConnectionsTotal.
  • SystemLogfile: Where Hiawatha should store logs for system messages and errors. Things like ban and unban events are recorded to the system log. Most people will want to use the default value here.
  • GarbageLogfile: Log file for all malformed HTTP requests. The types of things you might see in this log are open proxy requests, unsupported HTTP methods, and the contents of strange requests or attacks such as SQLI or BASH exploit attempts. Again, the majority should simply leave the default value here.
  • ThreadKillRate: How rapidly Hiawatha should kill off idle worker threads after a traffic spike. For the most part this setting will have little to no impact on how your site functions, but with the default value of '1' high traffic sites will see a lot of Hiawatha threads hanging around on their systems for a very long time. On the other hand, it's much more efficient to leave workers ready to handle new connections than to spin up new ones, so that might not be such a bad thing. On systems where there are many competing services and web service is not the central function of the host, you may want to set this value higher to kill off idle threads more aggressively. In all other instances, it should be set low for peak efficiency.
  • CacheSize: How large of a memory pool should Hiawatha set aside for the caching static assets to be served. On systems with a lot of assets and plenty of physical memory, you can set this as high as 1024MB, though that's probably overkill in most cases. For a single-instance WordPress host, a few MB is enough to speed up your site without over committing memory.
  • CacheMaxFilesize: Size in kilobytes of the maximum file size Hiawatha will cache. Smaller files are more expensive and less efficient to read off of disk, particularly from traditional spinning platters. If you have a limited amount of memory dedicated to caching, you should set the limit low enough so that you don't cache large files and unnecessarily tie up the cache in such a way that the smaller files get squeezed out.
  • MaxUrlLength: The maximum length of the path of an URL that the webserver accepts as being valid. Otherwise, a 414 error code is returned. The value 'none' disables this check. The default is 1000, which is also standard for many other webservers. If you know your valid request URLs will be much shorter, or that you've got some sloppy code that results in much longer valid URLs, adjust as necessary.
  • MinSSLversion: Actually a misnomer, since SSL support has been deprecated as of Hiawatha v9.9, the minimum TLS version Hiawatha accepts for HTTPS connections. For wide and general public consumption, TLS1.0 is probably sufficient. If you wish to only support TLS for the most modern web browsers, or if TLS will only be utilized for your web admin panel, you can utilize TLS1.2 as the minimum version for better security.
  • DHsize: The size of the Diffie-Hellman key used in HTTPS connections. The default value is 2048, which is considered acceptable in terms of security, but 4096 is a somewhat more future-proof approach at this point. 1024 is considered too weak and should no longer be uzed.
  • SocketSendTimeout: Sets the SO_SNDTIMEO value for all client connection sockets in seconds. This value is used to convey and enforce a hard timeout value for client socket connections. 30 seconds is usually more than reasonable for even high-latency mobile clients.
  • LogfileMask: List of IPs from which HTTP requests will be logged (or the contrary). If you use a health check service against your server, you may want to filter out the monitoring server's IP(s) to keep uninteresting log noise to a minimum. It's also useful for filtering out internal requests the server makes to itself, as is the case with WordPress' internal cron system and certain plugins.
  • RequestLimitMask: Define for which, if any, clients the ConnectionsPerIP, MaxRequestSize and TimeForRequest settings should not be used. Similar to how LogfileMask works, this is useful for making sure that external monitoring services and internal connections to the webserver don't get arbitrarily limited. In the example above, I've excluded the webserver's own IPs from request limits.

binding

# BINDING SETTINGS
# A binding is where a client can connect to.
#
Binding {
        Port = 80
        MaxKeepAlive = 100
        TimeForRequest = 5,15
        MaxRequestSize = 2000
        MaxUploadSize = 2
}

Binding {
        Port = 443
        MaxKeepAlive = 100
        TimeForRequest = 5,15
        SSLcertFile = /etc/ssl/localcerts/blog_example_tld.pem
        MaxRequestSize = 2000
        MaxUploadSize = 2
}

Binding {
        Port = 80
        Interface = MyIPv6
        MaxKeepAlive = 100
        TimeForRequest = 5,15
        MaxRequestSize = 2000
        MaxUploadSize = 2
}

Binding {
        Port = 443
        Interface = MyIPv6
        MaxKeepAlive = 100
        TimeForRequest = 5,15
        SSLcertFile = /etc/ssl/localcerts/blog_example_tld.pem
        MaxRequestSize = 2000
        MaxUploadSize = 2
}
  • Port: Which TCP port Hiawatha should bind to. Normally you'll be running on the standard canonical ports, which are of course 80 for HTTP and 443 for HTTPS.
  • Interface: If defined, which network interface Hiawatha should bind to. If undefined, Hiawatha will assume the default IPv4 interface for the system. If you have multiple addresses on your system, such as the co-existence of IPv6, you'll need to define specific bindings for each.
  • MaxKeepAlive: How many keep-alive requests a client is allowed to make for each particular connection. Keepalives can substantially improve the efficiency of your webserver, but setting this arbitrarily high can open you up to DoS and resource exhaustion attacks. A value of 100 is fairly standard across webservers, but if you have very few resources a client will be downloading in a given session, or the inverse, you should set this limit accordingly.
  • TimeForRequest: The time in which both the initial connection and each keep-alive connection have in which to complete, respectively. As with MaxKeepAlive, this option requires a little forethought. Setting either value too low may cause problems for clients with higher than average latency between themselves and your webserver, and setting them too high can allow clients to tie up your server's available slots for longer than necessary. Somewhere in the range of 3-5 seconds are usually quite reasonable for the initial connection, and 10-30 seconds are a sensible value for the entirety of each session.
  • SSLcertFile: If the port being bound is secure (HTTPS), this is how you can define the default TLS certificate for that instance. There can be only one default SSL certificate for each secure port, but you can also define this setting within a vhost in order to utilize Hiawatha's SNI support.
  • MaxRequestSize: The maximum size in kilobytes of a request the webserver will accept as legitimate. This doesn't include requests made using the PUT method, but as WordPress uses POST instead of PUT, this will be a limiting factor on how large of an attachment you can upload to WP through its admin interface. It's unusual to have assets larger than a couple of megabytes in WordPress, so you can usually set this value fairly low. Conversely if you know you'll be hosting larger files via WP, such as voluminous PDFs, you may need to adjust this value higher.
  • MaxUploadSize: The maximum size in megabytes of a PUT request entity the webserver is allowed to receive. This should ordinarily match the value you set for MaxRequestSize, though it usually won't matter much for WordPress in particular because most of its upload functions use POST rather than PUT for uploading files.

banning

# BANNING SETTINGS
# Deny service to clients who misbehave.
#
BanOnGarbage = 300
BanOnInvalidURL = 60
BanOnMaxPerIP = 15
BanOnMaxReqSize = 300
BanOnWrongPassword = 6:900
BanOnSQLi = 3600
KickOnBan = yes
RebanDuringBan = yes
BanlistMask = deny LOCALHOST, deny MyIPv4, deny MyIPv6
ChallengeClient = 768, javascript, 15
  • BanOnGarbage: Number of seconds to ban an IP in case of a malformed HTTP request ("400: Bad Request"). These really shouldn't happen during legitimate usage, so it's usually safe to ban clients who send garbage requests for a few minutes or more. I find 5 minutes (300 seconds) to be a prudent value.
  • BanOnInvalidURL: How long in seconds to ban users requesting URLs which contain invalid characters, including files beginning with a period (by convention hidden files on *nix systems; e.g. '.git'). This might conceivably occur by accident, for instance if a client typos a URL, so I tend to ban for a less excessive period of time than for some other perceived attacks. Something between 30-60 seconds is probably a fair value here.
  • BanOnMaxPerIP: If a client exceeds the limit set in ConnectionsPerIP, how long in seconds they should be banned. This is another setting which might conceivably be tripped by valid users, especially if many users are hitting your site from behind a shared IP (corporate firewall, proxy server, etc.). You can leave this setting off entirely in order to utilize Hiawatha's default drop tail behavior when ConnectionsPerIP is surpassed. Otherwise, this value acts as a cool-off period for clients making connections too aggressively. Unless you believe your server is or is likely to be under an extended DoS attack, this value can typically be set relatively low, e.g. 5-60 seconds.
  • BanOnMaxReqSize: Number of seconds to ban an IP in case of an overly large HTTP request (413: Request Entity Too Large). Unless your MaxReqSize is really conservative, this ban can be set for quite a while. I recommend 300 seconds.
  • BanOnWrongPassword: When using the PasswordFile option for HTTP 'basic' or 'digest' authentication on part of your site, how long in seconds to ban an IP if they exceed a certain threshold of failed authentication attempts. The first number corresponds to the number of attempts to ban on, and the second value determines how long they should be banned for. For instance, 6 attempts might trigger a ban for 15 minutes (900 seconds).
  • BanOnSQLi: How long to ban a client when they attempt a SQL injection attack. Unless your site is using some plugins or other custom code which results in some truly atrocious URLs, this is highly unlikely to be triggered by accident. As such, I find it completely justifiable to set this value for an hour (3600 seconds—now sit in the corner and think about what you did).
  • KickOnBan: Close all other connections that originate from the same IP in case of a ban. In other words, if a client IP address has 20 sessions open and the 21st does something warranting a ban, also terminate the previous 20 sessions. You'll probably want to say yes here in most cases.
  • RebanDuringBan: Whether or not Hiawatha should reset the ban timer when a client tries to reconnect during a ban. Again, this should probably be yes unless you have a good reason not to.
  • BanlistMask: As with other 'Mask' settings, which IP(s) or network(s) should be whitelisted from banning. This might include things like a legitimate security scanner service you use regularly, your office's static netblock, etc. Regardless, I'd recommend at least including the server's own IPs to prevent accidentally blocking internal connections WordPress might make to itself.
  • ChallengeClient: Challenge the client to verify that it's a real web browser and not a robot. This is valuable to prevent DDoS attacks, where lots of distributed hosts make as many connections as possible in order to tie up your server's resources and render it unusable for legitimate clients. When the total amount of server connections exceed the threshold you define, it works by challenging each subsequent request with either an HTTP "Set-Cookie" header or javascript, which presents a cryptographic challenge, ultimately resulting in a session cookie. If the client's cookie is not present or is incorrect in the context of the session, the connection is denied and the offending IP is banned for the amount of seconds you define.
    Of all the active security countermeasures in Hiawatha, ChallengeClient warrants the most caution. While this setting can be quite useful and effective at mitigating DDoS attacks, it will also have the effect of blocking legitimate bots while active, including webcrawlers like Googlebot, which ignore these methods. Should you choose to utilize it, the threshold for this behavior should generally be set around 10-25% lower than your server's peak throughput. In the case of my example server, I found through load testing that it nominally supports about 1,000 connections to WordPress, so I set this threshold to 768. One other 'gotcha' is that should you restart Hiawatha in while the ChallengeClient mode is active, the server's crypto seed will be randomly regenerated causing all client cookies will be invalidated. This means that if they haven't yet restarted their browser sessions causing their client cookie to be expunged (the cookie has a lifespan of only the active browser session and no longer), they too will be banned. The way to prevent this is to define your own secret as the last value after this string, which will persist between Hiawatha restarts. This value should be a random, alphanumeric string of up to 20 characters. For instance:*

    ChallengeClient = 768, javascript, 15, JJjhdi4Wg1iMLpXliWQ1

    If you don't want to risk using the ChallengeClient option, preferring instead to rely on ConnectionsPerIP exclusively to help prevent DoS attacks, you can leave this option off entirely to disable it.

*Don't use the 'secret' I've used in this illustration, obviously. If you want to set your own secret for 'ChallengeClient' to prevent the possible invalid cookie behavior I've outlined above, you can easily generate your own random strings with the Gen & Send service. Just check all of the option boxes except for "Punctuation", set the "Length" to 20, and click "Generate Password" until you're satisfied with the result. If you're going to hard code your secret rather than letting Hiawatha generate it on startup, I recommend periodically cycling it to a new random string.

cgi

# COMMON GATEWAY INTERFACE (CGI) SETTINGS
# These settings can be used to run CGI applications.
#
FastCGIserver {
        FastCGIid = PHP5
        ConnectTo = /var/run/php5-fpm.sock
        Extension = php
}
  • FastCGIid: The descriptive name of your fastCGI instance which you can refer to later in the VirtualHost context. If you plan to run multiple similar FCGI backends on the same host, you might make this more descriptive, e.g.
    FastCGIid = PHP-FPM-WP
  • ConnectTo: Either the UNIX socket or host:port of the FCGI service you wish to use. In this instance, I have used a socket via my PHP-FPM pool.
  • Extension: Which file extension(s) should be handled by the FCGI instance. In this case we're hosting WordPress, which strictly uses the '.php' extension.

urltoolkit

UrlToolKits are a rather involved topic which easily warrant their own article, so I won't be going into too much detail here. Instead I've provided some sample toolkits for you which can easily be adapted to your own needs, and describing what each one does.

# URL TOOLKIT
#
UrlToolkit {
    ToolkitID = cache-control
    Match ^/.*\.(css|eot|gif|htm|html|ico|jpeg|jpg|js|otf|pdf|png|ps|psd|svg|swf|ttf|txt|woff|woff2)(\?v=.*|\?ver=.*)?(/|$) Expire 1 weeks
}

In this particular toolkit, I'm defining my client-side cache control policy. Essentially, telling the client to hold on to the filetypes listed for the period of 1 week. See HTTP caching in Hiawatha for more information.

UrlToolkit {
    ToolkitID = wordpress
    RequestURI exists Return
    Match .*\?(.*) Rewrite /index.php?$1
    Match .* Rewrite /index.php
}

This is how we get our pretty, SEO-friendly URLs in WordPress. Note the first line in particular; it tells Hiawatha that if a file exists in the path on disk, it should be served by Hiawatha directly, rather than through PHP, for maximum efficiency. Therefore, there's no need for something like X-SENDFILE.

UrlToolkit {
    ToolkitID = secure-wp
    UseSSL Skip 2
    Match ^/wp-login.php(.*) Redirect https://blog.example.tld/wp-login.php$1
    Match /wp-admin/$ Redirect https://blog.example.tld/wp-admin/$1
}

In this toolkit, I'm first checking if the client is connecting with SSL [TLS]. If not, I'm matching 'wp-login.php' and '/wp-admin/' and forcing HTTPS for URLs which include them. This ensures that connections to the WP control panel are always encrypted in order to keep your credentials from ever being sent over the Internet in clear text. This is optional, but strongly recommended.

default website

# DEFAULT WEBSITE
# It is wise to use your IP address as the hostname of the default website
# and give it a blank webpage. By doing so, automated webscanners won't find
# your possible vulnerable website.
#
Hostname = 127.0.0.1
WebsiteRoot = /var/www/hiawatha
StartFile = index.html
AccessLogfile = /var/log/hiawatha/access.log
ErrorLogfile = /var/log/hiawatha/error.log
#ErrorHandler = 404:/error.cgi

The default website in Hiawatha is basically just a standard vhost. The difference is that this is what will be served up in case the client makes a connection to a domain you're not expecting to answer for. Very often network vulnerability scanners won't specifically scan your host by its proper DNS name, but by IP instead. In that case they'll see only a very basic website with no interesting CGI, databases, etc. Therefore it's considered best practice to serve nothing but a dummy page on the default website. The stanza above is right out of the vanilla hiawatha.conf, but feel free to make your own default page and change the 'WebsiteRoot' to point to that instead.

Here's a boilerplate of a really basic redirector page you might wish to adapt for your own default vhost:
Default vhost 'index.html' example

Raw text version here.

virtual hosts

# VIRTUAL HOSTS
# Use a VirtualHost section to declare the websites you want to host.
#
VirtualHost {
        Hostname = blog.example.tld, *.blog.example.tld
        WebsiteRoot = /srv/www/vhosts/blog_example_tld
        StartFile = index.php
        TimeForCGI = 60
        UseFastCGI = PHP5
        CustomHeader = X-Frame-Options: sameorigin
        CustomHeader = Vary: Accept-Encoding
        RandomHeader = 64
        UseToolkit = secure-wp, wordpress, cache-control
        EnforceFirstHostname = yes
        PreventXSS = yes
        PreventCSRF = yes
        PreventSQLi = yes
}

This is the vhost for our blog domain, 'blog.example.tld' in this illustration. Note that on my own blog, I'm not using a prefix domain at all, simply 'dotbalm.org'. Use whichever approach best suits your tastes.

  • Hostname: The DNS host name(s) the vhost should respond to. This can be a single canonical host name or a comma-space delimited list of names.
  • WebsiteRoot: The path to where WordPress lives on disk. I use '/srv/www/vhosts/' as a convention, since /srv/ is not utilized by any standard software packages (as opposed to '/var/www/' which is used by many packages).
  • StartFile: The primary index file for the site if one is not specifically called in the URL. For WordPress, this should be 'index.php'.
  • TimeForCGI: How long in seconds CGI processes are allowed to run. On your average server, 30-60 seconds is plenty.
  • UseFastCGI: Which fastCGI handler should be used for this vhost. Use the one you defined for WordPress in your FastCGIserver stanza, e.g. 'PHP5'.
  • CustomHeader: Sends a custom HTTP header to the client. In the sample config I've provided, I'm sending a header for variable content encoding ('Vary: Accept-Encoding' for HTTP gzip compression)* and one to make sure the site can't be hijacked via frames ('X-Frame-Options: sameorigin').
  • RandomHeader: One of the nice Hiawatha-specific features available to you, RandomHeader adds an 'X-Random' HTTP header to the response for HTTPS connections. The header contains a random string, the length of which is a random value between 1 and the length you specify. The value given must be between 10 and 1000. The X-Random header is not parsed by client browsers, but helps to mitigate side-band cryptographic attacks by padding out the session content.
  • UseToolkit: Which URL toolkit(s) the vhost should utilize, in the order they will be called. In this instance I've called my 'secure-wp' toolkit to force TLS for my admin panel, 'wordpress' to make WP's SEO URLs function correctly, and 'cache-control' to specify client cache duration for static assets.
  • EnforceFirstHostname: If you've specified more than one Hostname for the vhost, you can use this option to force clients to use the first one listed via a 301 response (permanent redirect).
  • PreventXSS: Prevent cross-site scripting via the URL by replacing a less-then, greater-then, quote or double-quote in the URL with an underscore.
  • PreventCSRF: Prevent Cross-site Request Forgery by ignoring all cookies sent by a browser when following an external link to this website.
  • PreventSQLi: Prevent SQL-injection attacks by detecting apparent injections and denying the request via a 409 response.

*NOTE: You should leave the 'Vary: Accept-Encoding' header off if you'll be using the 'gzip compression' option in the WP Super Cache plugin, as it too will send the same header. In fact, I propose you do exactly that for optimal performance. This is covered in a later section, "Optional: Installing WP Super Cache" in greater detail.

directories

# DIRECTORY SETTINGS
# You can specify some settings per directory.
#
Directory {
        Path = /srv/www/vhosts/blog_example_tld/wp-content/themes
        UseGZfile = yes
}

Here I'm making use of Hiawatha's UseGZfile option to serve a pre-compressed version of my WP theme. This is purely elective, but can help to substantially reduce the size of your theme, thereby improving latency for your users and reducing bandwidth usage. Note if you wish to use this method, you'll first need to actually gzip each relevant file within your theme's directory individually before this option will do anything. See my previous article, " Using Hiawatha’s compression method, the 'UseGZfile' option" for more info (it also includes a handy script which can do all the work for you, hint, hint).

Once you've finished configuring Hiawatha, you can restart it to put the changes into effect:

sudo /etc/init.d/hiawatha restart

Installing WordPress

Now that we've got the rest out of the way, installing WordPress is the easy part.

Getting WordPress

First, we download it to a temporary location on our host:

cd /tmp && wget https://wordpress.org/latest.tar.gz


If the download goes as expected, we can extract it in place:

tar xzf latest.tar.gz


You should now have a directory called 'wordpress' in your /tmp directory. Copy its contents to its new home in the path it'll live in. I use '/srv/www/vhosts/sitename' as a convention, so I'd do something like:

sudo mkdir -p /srv/www/vhosts/blog_example_tld
sudo cp -r /tmp/wordpress/* /srv/www/vhosts/blog_example_tld


For WordPress to be able to do auto-updates, allow you to upload content via the UI, install plugins, etc. the WP directory will also need to be owned by your webserver's user ID. This isn't strictly necessary, but definitely provides the most straightforward WordPress experience:

chown -R www-data:www-data /srv/www/vhosts/blog_example_tld

Setting up a database for WordPress

Before you can perform the initial configuration, you'll also need to prep a database for WordPress to use. First, gain a root MySQL console:

mysql -u root


Then you can add an empty database and an unprivileged user to own it. Replace instances of 'myblog' with the actual name you want to use for your database and its user, and '***' with a real password. I recommend this password be between 8-16 alphanumeric characters (Gen & Send is always useful if you need to quickly generate a random one).

CREATE USER 'myblog'@'%' IDENTIFIED BY '***';
GRANT USAGE ON *.* TO 'myblog'@'%' IDENTIFIED BY '***' WITH MAX_QUERIES_PER_HOUR 0 MAX_CONNECTIONS_PER_HOUR 0 MAX_UPDATES_PER_HOUR 0 MAX_USER_CONNECTIONS 0;
CREATE DATABASE IF NOT EXISTS `myblog`;
GRANT ALL PRIVILEGES ON `myblog`.* TO 'myblog'@'%';
QUIT;


Keep the credentials you've set here handy; you'll need them momentarily.

Configuring WordPress

Once you've got WordPress in place and a database prepped for it, you can start the friendly web-based configuration process.

For purposes of illustration, I've captured screenshots of a real WordPress instance, "example.dotbalm.org". Obviously you'll need to substitute the name of your own host. You may have also noticed that the SSL certificate on my example site isn't signed. I recommend that for a real site, you acquire a valid cert from a reputable certificate authority.

Start by connecting to the host, e.g. "http://example.dotbalm.org/". You'll be prompted to do some basic configuration in order to get your site running.
WordPress database setupAs you can see from the screenshot, because of the 'secure-wp' UrlToolkit I specified in my hiawatha.conf, the connection for the administrative panel is automatically redirected from HTTP to HTTPS.

You will be required to setup your database connection. Use the credentials you set earlier when you configured your WP database.

WordPress General SettingsUnder Settings General in the admin panel, you can do some fine tuning to get the generalities of the site just how you want them. For the 'URL' fields, make sure the protocol which preceeds your site address is correct for what you want clients to experience. For better or worse, WordPress relies on the protocol you've set here when formulating links to all content on your blog. To rephrase, if you want your site to be TLS all the time, use "HTTPS" for the protocol. If not, use "HTTP".

WordPress Permalink SetupNext you should setup your URL structure from Settings Permalinks. Personally, I like to use /%postname%/ as I find it gives the cleanest, most concise results. But, it's entirely up to your own preference. Just make a point of setting it once and keeping it that way, so that you don't end up with dead links later on.

After the WordPress instance is configured and working, you'll want to change your site's keys and salts to be unique. The easiest way is to utilize wordpress.org's secret-key API service:

curl https://api.wordpress.org/secret-key/1.1/salt/

Simply copy the output, and use it to replace the relevant section in your site's wp-config.php.

Optional: Installing WP Super Cache

While installing the WP Super Cache plugin isn't strictly necessary, I highly recommend it to get the best possible performance and scalability out of your WordPress site.

First, navigate to Plugins Add Plugins from within your admin panel.
WordPress Install WP Super CacheSearch for "wp super cache" using the search dialog. From the results, locate the "WP Super Cache" plugin by Automattic and click its "Install Now" button. When prompted if you're sure you want to install this plugin, click "OK".

Once it's installed, go to Plugins Installed Plugins and click "Activate" from within the "WP Super Cache" section, then click "Settings", and the "Advanced" tab.

WP Super Cache SettingsYou've got quite a bit of flexibility within the plugin, but I recommend enabling at least the following options:

  • Cache hits to this website for quick access.
  • Use PHP to serve cache files.
  • Compress pages so they’re served more quickly to visitors.
  • 304 Not Modified browser caching.
  • Don’t cache pages for known users.
  • Cache rebuild.

WP Super Cache Advanced SettingsAdditionally, in the "Advanced" section:

  • Clear all cache files when a post or page is published or updated.
  • Extra homepage checks.
  • Only refresh current page when comments made.

Afterwards, click the "Update Status" button. Once your changes have been saved, click the "Preload" tab.
WP Super Cache SettingsThere, set a value for "Refresh preloaded cache files every ___ minutes." If you update your blog infrequently, this can be every few hours or so, or perhaps less if you're making new posts several times daily. Also check the boxes next to "Preload mode" and "Preload tags, categories and other taxonomies." Finally, click the "Update Settings" button.

Now you can test to make sure WP Super Cache is doing its job properly. From a browser which is not currently logged into your WordPress site, you should see the following section from within the page source, at the very bottom:
WP-Super-Cache tags

Results

Congratulations! Your WordPress site is configured and ready for your first post. If you followed this guide fairly closely, you've probably also got one of the fastest and most defensible WordPress instances in the wild today, as Hiawatha is capable of blocking a majority of the security threats it will face on a daily basis.
WordPress Hiawatha Example site

Benchmarking my own example site with ab, I was able to demonstrate more than respectable results, despite the fact that the server instance it ran on was extremely tiny:
Hiawatha + WordPress, 1 core, 256MB RAM: 1030 requests/s
The results from Pingdom Tools confirm this by stating that, even with the handicap of using the rather heavy default WordPress 2015 theme, my example site was still faster than 99% of all tested sites.
Speed Test results on WordPress (stock Twenty Fifteen theme) - 209ms from San Jose, CA

Whew, that about does it. Thanks for bearing with me through what became an insanely long-winded article. Frankly, it didn't occur to me what all was involved in a task that normally only takes me 15 minutes until I sat down to write about it. Still, I hope you've found it helpful. That'd certainly make the whole process worthwhile.

Questions? Suggestions? Haikus? Dirty limericks? Leave a reply in the comments.

38 thoughts on “Hosting WordPress with Hiawatha

    • Hey there Hugo. I hadn’t heard about your firewall script, but thanks for letting me know. I’ll check it out.

  1. Hi,

    Thank you for this great tutorial.
    Could you please tell me if you have any idea on what the UrlToolkit should look like for a wordpress multisite using sub directory would be?
    This is what I tried to use but the code isn’t working..

    UrlToolkit {
           ToolkitID = multiwp
           Call scannerblocker
           Match ^/index\.php$ Return
           Match ^/([_0-9a-zA-Z-]+/)?wp-admin$ Redirect /$1wp-admin/
           RequestURI exists Return
           Match ^/([_0-9a-zA-Z-]+/)?(wp-(content|admin|includes).*) Rewrite /$2
           Match ^/([_0-9a-zA-Z-]+/)?(.*\.php)$ Rewrite /$2
           Match .* Rewrite /index.php
    }

    Thank you
    Fred

    • Here’s what I did to get a multisite install using directories (rather than subdomains) working.

      URLToolkits:

      UrlToolkit {
          ToolkitID = cache-control
          Match ^/.*\.(css|eot|gif|htm|html|ico|jpeg|jpg|js|otf|pdf|png|ps|psd|svg|swf|ttf|txt|woff|woff2)(\?v=.*|\?ver=.*)?(/|$) Expire 1 months
      }
      
      UrlToolkit {
          ToolkitID = wp-multi
          Match ^/index\.php$ Return
          Match ^/([_0-9a-zA-Z-]+/)?wp-admin$ Redirect /$1wp-admin/
          RequestURI exists Return
          Match ^/([_0-9a-zA-Z-]+/)?(wp-(content|admin|includes).*) Rewrite /$2
          Match ^/([_0-9a-zA-Z-]+/)?(.*\.php)$ Rewrite /$2
          Match .* Rewrite /index.php?$1
      }

      And then called them in the relevant vhost:

      UseToolkit = wp-multi, cache-control

      You also have to follow the usual process for configuring your WP instance to be multisite.

  2. Thank you very much for your reply 🙂

    My last question is: Do you still use ToolkitID = secure-wp in this scenario?

    I really appreciated your help.

    I’ll try these setting first thing on Tuesday 🙂

  3. Thank you very much for your support 🙂
    I have manged to get wordpress multisite to work like a charm.
    I have also noticed that I i set EnforceFirstHostname = yes, I cannot access my site.
    In my case I had to set it to EnforceFirstHostname = no…

    Could you please tell me why you set this value to yes?

    Thank you

    • That particular option is useful if you want a vhost to listen for multiple domain names, but you want traffic to be directed to a particular one. It saves you the trouble of writing a UrlToolkit just for that, basically.

  4. Hi Chris,

    Awesome tutorial, thanks very much.

    Do you think that it’s possible to enable hiawatha’s reverse proxy in your configuration ?

    Regards

    • Thanks for the feedback. I’m not 100% sure of what you’re looking for, do you mean an example of how to use the reverse proxy functionality of Hiawatha? It’s pretty simple syntax. For example, the following would forward all requests for a particular vhost to a downstream server:

      ReverseProxy = .* http://example.tld 30 keep-alive

      • Thanks for your reply.
        Sorry my question is not really clear. In few words, is it possible to use the reverse proxy and server functionnalities on the same instance ?

        For example, is this configuration will be correct :

        VirtualHost {
                Hostname = www.example.tld
                WebsiteRoot = /srv/www/vhosts/public_html
                StartFile = index.php
                TimeForCGI = 60
                UseFastCGI = PHP5
                UseToolkit = wordpress, cache-control
                ReverseProxy = .* http://example.tld 30 keep-alive
        }

        Is the ReverseProxy configuration must be outside VirtualHost ?

        • Yes, you can mix reverse proxied content into a vhost that’s also doing CGI, and no, it should be inside a vhost stanza. The trick is choosing what should and shouldn’t be proxied. In the example I gave you earlier, I used a wildcard that matches everything, so every hit to that particular vhost would be proxied to the downstream host. That’s probably not what you want. For example, if I only wanted to proxy a particular directory (say, one with icons in it), I could do something like this:

          ReverseProxy ^/icons http://resources.lan/images


          That’s matching the /icons path in the vhost, and proxying those requests to a particular directory of a downstream server. See the ReverseProxy entry of the Hiawatha manpage.

  5. Hi,

    I am using wordpress 4.2.2 multisite with the following rewrite rules
    UrlToolkit {
    ToolkitID = wp-multi
    Match ^/index\.php$ Return
    Match ^/([_0-9a-zA-Z-]+/)?wp-admin$ Redirect /$1wp-admin/
    RequestURI exists Return
    Match ^/([_0-9a-zA-Z-]+/)?(wp-(content|admin|includes).*) Rewrite /$2
    Match ^/([_0-9a-zA-Z-]+/)?(.*\.php)$ Rewrite /$2
    Match .* Rewrite /index.php?$1
    }
    When in the main site I have no probleme to login or logout but when I try to login to the other sites, I get a 404 error message page.

    I have noticed that when I am not on the main site and try to logout, the url is http://.com/wp-login.php?action=logout&_wpnonce=ecf93ddc7d

    Could you please tell me if you can help me here?
    Is the problem with the rewrite rules that I use?

    Thank you in advance
    Fred

  6. Wow! Very nice .. and YES, LONG article!

    But it’s very detailed which is awesome. I’m gonna test the Hiawatha webserver on a digitalocean vm this evening.

    My Q: Is this article still valid for the versions of wordpress/hiawathe we use these days?

    Thnx!

    • Yes, still valid. The only difference to keep in mind is that some minor things in the syntax have changed as of Hiawatha 9.13; specifically “SSL” has been replaced with “TLS”. For instance, ‘MinSSLversion’ is now ‘MinTLSversion’. The old syntax will still work for now, but may be deprecated in a later version.

  7. Hi Chris,

    I managed to work out the rewrite rule that will work with wordpress 4.2.2 and Hiawatha v9.12.
    In addition to the code you have me, I had to add the following code for wordpress to behave correctly..

    Match ^/[_0-9a-zA-Z-]+(/wp-.*) Rewrite /$1

    to the complete toolkit is

    UrlToolkit {
    ToolkitID = wp-multi-subdir
    Match ^/index\.php$ Return
    Match ^/([_0-9a-zA-Z-]+/)?wp-admin$ Redirect /$1wp-admin/
    RequestURI exists Return
    Match ^/([_0-9a-zA-Z-]+/)?(wp-(content|admin|includes).*) Rewrite /$2
    Match ^/([_0-9a-zA-Z-]+/)?(.*\.php)$ Rewrite /$2
    Match ^/[_0-9a-zA-Z-]+(/wp-.*) Rewrite /$1 # if not present 404 – error is displayed
    Match .* Rewrite /index.php?$1
    }

    Hope this help someone else here.

    NOTE: this is tested for a wordpress 4.2.2 multisite installation using subdirectories (rather than subdomains)

  8. Hi Chris! I’ve been trying to configure permalinks on WordPress on my website. I currently have it running under /blog, and the problem seems to be that the UrlToolkit information you provided works if the blog is in the root directory. Regular expressions aren’t really my thing so I’ve been having a hard time rewriting the rules to accommodate for this. How could I modify them to take this into account?

    • I didn’t cover WP-Multisite in this tutorial, since it’s a bit of a minefield–you can see how long this article is without even tackling that subject. That said, take a look at Fred’s comment just above your own. You should be able to utilize his toolkit by simply appending /blog/ to each relevant Match line.

  9. hi when i fallow steps for installation i create new ssh username and password but when it restarting i cant connect with my vm. what is mean?:::

    “Once you’ve copied your SSH stanza to ~/.ssh/config, allow the server to reboot.
    After 10 seconds or so, you can SSH to your new VPS, e.g. “myhost”: “

    • In the tutorial for the Ramnode Primer Script, please pay very close attention to step #5. That’s the part you’re missing. As for the other bit you asked about: On Linux/Unix/OS X systems your user-specific SSH configuration is located in ~/.ssh/config, where `~` is a placeholder for your home directory. The stanza outputted at the end of the script run is formatted for you to simply add it to your local SSH configuration file (simply create a new one if it doesn’t exist). If you’re using Windows with PuTTY or something other than an actual native SSH client, just record the information and add it as expected. Note that SSH is listening on a randomly-determined port in your SSH stanza. That is very important. If you try to SSH to the standard port (22), your connection will simply time out.

  10. Thanks very much! This is one of the better, more clearly written articles on the Hiawatha webserver.

    I can now confirm that it works on a Ramnode KVM VPS, minimal install of Ubuntu 14.04 x86_64 server.

      • By the way, I noticed that you use WP Super Cache. Though i have never used it, I have used WP Total Cache (good but complex). My current favorite is WP Rocket (also good, but fewer configuration options).

        However, it is my understanding that all of these plugins write their configuration to the .htaccess file.

        How does this work in Hiawatha?

        I don’t know that I’ll need it for a personal blog, as I’ve already got Cloudflare [free] set up as DNS/proxy/etc.

        But, I am curious. After all, one can never have too much speed 🙂

        • WP Total Cache tends to cause more problems than it solves, so I’ve avoided it for several years. I use WP Super Cache in PHP mode with cache warming enabled (walkthrough in the article above; go to the “Installing WordPress” section and scroll down to “Optional: Installing WP Super Cache”). WP Super Cache doesn’t require .htaccess, so it’s basically a non-op to get rolling. With a nice fast setup like yours (Hiawatha + RamNode), a CDN will most likely add latency rather than reduce it. 🙂 As for .htaccess, that’s also addressed in the article. That method has potential security and performance issues associated with it, and Hiawatha doesn’t work that way. Instead, it utilizes URL toolkits, which are basically rewrite stanzas.

  11. Pingback: Trusted domain | CyberXpert knowledge Center

  12. Hello, I try to install Piwik on Debian Jessie / Hiawatha, but I have a “404 error”.
    My site is working properly, but I can not load the installer piwigo.
    What should I check?
    Hiawatha.conf ?, php?

    In /etc/php5/fpm/php.ini only I changed this value: cgi.fix_pathinfo = 0
    /etc/php5/fpm/pool.d/www.conf was as follows:
    listen.owner = www-data
    listen.group = www-data
    listen.mode = 0660
    And… hiawatha.conf (part):
    # CGIhandler = /usr/bin/perl:pl
    CGIhandler = /usr/bin/php-cgi:php
    # CGIhandler = /usr/bin/python:py
    # CGIhandler = /usr/bin/ruby:rb
    # CGIhandler = /usr/bin/ssi-cgi:shtml
    # CGIextension = cgi
    #
    FastCGIserver {
    FastCGIid = PHP5
    ConnectTo = /var/run/php5-fpm.sock
    Extension = php, php5
    }
    # URL TOOLKIT do not touch it
    # DIRECTORY SETTINGS do not touch it
    VirtualHost {
    Hostname = sastreriaruben.com,www.sastreriaruben.com
    WebsiteRoot = /var/www/sastreriaruben.com/public_html
    StartFile = index.html
    AccessLogfile = /var/www/sastreriaruben.com/log/access.log
    ErrorLogfile = /var/www/sastreriaruben.com/log/error.log
    TimeForCGI = 15
    UseFastCGI = PHP5
    }
    Thanks for your support

  13. Hello Chris, Thanks for clear, in deep tutorial,
    could you please, share your experience about tuning and hardening server

    • Hi there,

      I’m not sure what you’re getting at here. Do you mean security in the context of Hiawatha + WordPress? Because if you read the article you’ve replied to, you’ll find quite a lot on security (especially here). Or are you suggesting I write an article about full-stack hardening?

      -C

      • I’m Sorry if my question confuse you,
        i have implemented some of in this tutorial like fail2ban, php. And i’m digging into your ramnode script and implement sysctl section. Your site in my second documentation after official hiawatha site, Thanks for your great tutorial. 🙂

  14. Hi Chris,

    I know my question is a bit off topic but I was advised by Hugo to get in touch with you.
    I am using 2 hiawatha webserver.
    Server 1 is reverse proxy
    Server 2 host all wordpress site

    Could you please tell me if you ever set some rewite rule that will work with woocommerce and wordpress?
    At the moment I am using the following code for hiawatha:

    UrlToolkit {
    ToolkitID = wordpress
    RequestURI exists Return
    Match .*\?(.*) Rewrite /index.php?$1
    Match .* Rewrite /index.php
    }

    UrlToolkit {
    ToolkitID = secure-wp
    UseTLS Skip 2
    Match ^/wp-login.php(.*) Redirect https://www.softfloor-outlet.co.uk/wp-login.php$1
    Match /wp-admin/$ Redirect https://www.softfloor-outlet.co.uk/wp-admin/$1
    }

    Ad in my wordpress (single installation), I have

    define( ‘FORCE_SSL_ADMIN’, true );
    define( ‘FORCE_SSL_LOGIN’, true );

    I also tick the box in woocommerce asking to ‘Force secure checkout’

    So far here is what I get:
    mydomain.com/wp-admin -> green padlock (but disapear once login)
    mydomain.com/my-account -> padlock apear briefly and go away = no padlock
    mydomain.com//checkout -> padlock apear briefly and go away = no padlock

    Hope you can help

    Fred

    • I’ve had this solution working for my buddy’s e-commerce platform (https://www.dynamicdyna.com/), though I find the best approach with e-commerce sites in general is simply forcing full-time HTTPS for that vhost. That’ll give your customers better overall safety and make your site more trustworthy in appearance, as well.

      I’d do that at the Hiawatha-level, as that doesn’t require any of the crappy WooCommerce code to function as expected if a bug is introduced:
      RequireTLS = yes,31536000
      It’ll also earn you an A+ from SSLLabs.

  15. Hi Chris,
    Please let me know if you could update the RamNode provisioning script as RamNode does not offer OpenVZ VPS Debian 7 64-bit, only Debian 8 is available to install. Please advise.
    Thanks, Attila

    • Thanks for the heads-up; I’m glad to hear they’ve finally caught up to a newer LTS release of Debian. I’ll get to work revamping the old script to work with Debian 8 (supported until the end of April, 2020).

    • I’ve updated my RamNode bootstrap script, which can be found here, to expect Debian 8.x (x86_64) rather than 7.x. It will work on either the standard or minimal installation options as a starting point. Don’t forget to record your SSH stanza when it finishes as it chooses a random, ethereal port for SSH to run on by default. If you don’t want that behavior, you can always edit the SSH_PORT="" section near the top of the script to define your own port (e.g. 22) before running the script. There’s also a new stand-alone article about the ramnode-bootstrap script here.

      Best regards,
      -Chris

Leave a Reply

Your email address will not be published. Required fields are marked *