Configure Apache Server and Deploy WordPress with Puppet

We’re going to use Puppet to install Apache and WordPress.

This article is part of the Homelab Project with KVM, Katello and Puppet series.


We have two CentOS 7 servers installed which we want to configure as follows:

web1.hl.local ( – Apache server with WordPress and NFS mount
web2.hl.local ( – Apache server with WordPress and NFS mount

SELinux set to enforcing mode.

See the image below to identify the homelab part this article applies to.

WordPress from a Custom Tarball

We won’t be downloading WordPress from the Internet, but will be using our own custom build (tarball) instead. Each build is source controlled and tested locally prior releasing to the environment.

The name of the tarball is wordpress-latest.tar.gz, and it’s currently stored on the Katello server under /var/www/html/pub/. This allows us to pull the archive from https://katello.hl.local/pub/wordpress-latest.tar.gz.

Note that the file wp-config.php is never stored inside the tarball, but gets generated by Puppet.

Redundand Apache/MySQL Architecture

To increase redundancy, each Apache server will be configured to use a different MySQL database server.

Since our MySQL nodes are configured to use a Master/Master replication, there should, in theory, be no difference in terms of data that’s stored on each VM.

We’ll plug web1.hl.local in to db1.hl.local, and web2.hl.local in to db2.hl.local.

NFS Mount for Uploads

Both Apache servers will need an NFS client configured to mount shared storage.

While users can use either of the Apache servers to upload files, we need to ensure that regardless of the VM they end up on, files across WordPress instances are the same. We could use rsync to solve the problem, but I’m not sure on how well that would scale. Perhaps something to look at in the future.

Configuration with Puppet

Puppet master runs on the Katello server.

Puppet Modules

We use the following Puppet modules:

  1. derdanne-nfs – to mount an NFS share for WordPress /uploads folder
  2. puppetlabs-apache – to install and configure Apache
  3. puppet-selinux – to configure SELinux booleans (e.g. httpd_use_nfs)
  4. hunner-wordpress – to install WordPress

Please see each module’s documentation for features supported and configuration options available.

Firewall Configuration

Configure both Apache servers to allow HTTPS traffic:

firewall { '007 allow HTTPS':
  dport  => '443',
  source => '',
  proto  => tcp,
  action => accept,

There may be an insignificant performance penalty incurred while using encryption between HAProxy and Apache compared to plaintext HTTP, but we want to ensure that traffic is secured. HAProxy can be re-configured to offload TLS if this becomes a problem.

SELinux Booleans

Configure SELinux on both Apache servers. These are required in order to allow Apache to use NFS and connect to a remote MySQL instance. We also want to allow Apache (WordPress) to send email notifications.

selinux::boolean { 'httpd_use_nfs': 
  persistent => true, ensure => 'on' 
selinux::boolean { 'httpd_can_network_connect_db': 
  persistent => true, ensure => 'on' 
selinux::boolean { 'httpd_can_sendmail':
  persistent => true, ensure => 'on' 

Configure NFS Client

This needs to be applied for both Apache servers.

class { '::nfs':
  server_enabled => false,
  client_enabled => true,
nfs::client::mount { '/var/www/html/wp-content/uploads':
  server => 'nfsvip.hl.local',
  share => '/nfsshare/uploads',

The virtual IP (which NFS cluster runs on) is, and the DNS name is nfsvip.hl.local. The cluster is configured to export /nfsshare. See here for more info.

Install Apache

This needs to be applied for both Apache servers.

We deploy one Apache virtualhost, and configure log forwarding to Graylog (see the custom_fragment section). I wrote a separate post for how to send Apache logs to Graylog, take a look here if you need more info.

TLS certificates are taken care of by the main Puppet manifest for the environment. See here for more info.

package { 'php-mysql': 
  ensure => 'installed' 
class { 'apache':
  default_vhost     => false,
  default_ssl_vhost => false,
  default_mods      => false,
  mpm_module        => 'prefork',
  server_signature  => 'Off',
  server_tokens     => 'Prod',
  trace_enable      => 'Off',
  log_formats       => { graylog_access => '{ \"version\": \"1.1\", \"host\": \"%V\", \"short_message\": \"%r\", \"timestamp\": %{%s}t, \"level\": 6, \"_user_agent\": \"%{User-Agent}i\", \"_source_ip\": \"%h\", \"_duration_usec\": %D, \"_duration_sec\": %T, \"_request_size_byte\": %O, \"_http_status_orig\": %s, \"_http_status\": %>s, \"_http_request_path\": \"%U\", \"_http_request\": \"%U%q\", \"_http_method\": \"%m\", \"_http_referer\": \"%{Referer}i\", \"_from_apache\": \"true\" }' },
include apache::mod::alias
include apache::mod::headers
include apache::mod::php
include apache::mod::rewrite
include apache::mod::ssl
::apache::mod { 'logio': }

## Configure VirtualHosts
apache::vhost { 'blog_https':
  port         => 443,
  servername   => 'blog.hl.local',
  docroot      => '/var/www/html',
  manage_docroot => false,
  options      => ['FollowSymLinks','MultiViews'],
  override     => 'All',
  suphp_engine => 'off',
  ssl          => true,
  ssl_cert     => '/etc/pki/tls/certs/hl.crt',
  ssl_key      => '/etc/pki/tls/private/hl.key',
  ssl_protocol => ['all', '-SSLv2', '-SSLv3'],
  ssl_cipher   => 'HIGH:!aNULL!MD5:!RC4',
  ssl_honorcipherorder => 'On',
  redirectmatch_status => ['301'],
  redirectmatch_regexp => ['(.*)\.gz'],
  redirectmatch_dest   => ['/'],
  custom_fragment      => 'CustomLog "|/usr/bin/nc -u syslog.hl.local 12201" graylog_access',

Install WordPress from Our Custom Tarball

We want to use our existing MySQL configuration, meaning that we don’t want WordPress creating any databases nor users. The details below are the ones we used when setting up MySQL servers.

The only thing that’s going to different here is the db_host parameter – one Apache server uses db1.hl.local, another one uses db2.hl.local.

class { 'wordpress':
  db_user        => 'dbuser1',
  db_password    => 'PleaseChangeMe',
  db_name        => 'blog',
  db_host        => 'db1.hl.local',
  create_db      => false,
  create_db_user => false,
  install_dir    => '/var/www/html',
  install_url    => 'http://katello.hl.local/pub',
  version        => 'latest',
  wp_owner       => 'apache',
  wp_group       => 'root',

Note how we set the owner to apache. This is something we may want to harden further depending on security requirements.

If all goes well, we should end up with both servers using the NFS share:

[root@web1 ~]# df -h|egrep "File|uploads"
Filesystem                         Size  Used Avail Use% Mounted on
nfsvip.hl.local:/nfsshare/uploads  2.0G   53M  1.9G   3% /var/www/html/wp-content/uploads
[root@web2 ~]# df -h|egrep "File|uploads"
Filesystem                         Size  Used Avail Use% Mounted on
nfsvip.hl.local:/nfsshare/uploads  2.0G   53M  1.9G   3% /var/www/html/wp-content/uploads

We should also be able to create Graylog dashboard widgets by using Apache data, e.g.:

What’s Next?

We’ll look into putting a pair of HAProxy servers in front of Apache to perform load balancing.

Leave a Reply

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