Category Archives: WordPress

Updating Caddy to PHP 7.2

This was wonderfully easy.

sudo apt update && sudo apt dist-upgrade -y && sudo apt autoremove -y && sudo apt autoclean
sudo apt install php7.2-cli php7.2-common php7.2-fpm php7.2-json php7.2-mysql php7.2-opcache php7.2-readline

Update Caddyfile:

sudo vim /etc/caddy/Caddyfile {
        } {
        root /var/www/yawnbox/
        log stdout
        errors stderr

header / {
        Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline';"
        Expect-CT "enforce; max-age=7073887;"
        Referrer-Policy "strict-origin, strict-origin-when-cross-origin"
        Strict-Transport-Security "max-age=15768000; includeSubDomains; preload"
        X-XSS-Protection "1; mode=block"
        X-Content-Type-Options "nosniff"
        X-Frame-Options "DENY"

fastcgi / /var/run/php/php7.2-fpm.sock {
        ext .php
        split .php
        index index.php

rewrite / {
        to {path} {path}/ /index.php?{query}

tls {
        protocols tls1.2
        curves p384
        key_type p384
sudo service caddy restart
sudo apt remove php-mysql php7.1-cli php7.1-common php7.1-fpm php7.1-json php7.1-mysql php7.1-opcache php7.1-readline

Moved from Apache to Caddy and RSA to EC TLS for WordPress

^ Qualys SSL Labs test for

^ Security Headers (dot-IO) test for

With very special thanks to this guide, Running WordPress with Caddy. I was also able to remove several unnecessary PHP applications that Apache needed.

Here’s my Caddyfile: {
        } {
        root /var/www/
        log stdout
        errors stderr

header / {
	Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'"
	Referrer-Policy "strict-origin, strict-origin-when-cross-origin"
        Strict-Transport-Security "max-age=15768000; includeSubDomains; preload"
        X-XSS-Protection "1; mode=block"
        X-Content-Type-Options "nosniff"
        X-Frame-Options "DENY"

fastcgi / /var/run/php/php7.1-fpm.sock {
        ext .php
        split .php
        index index.php

rewrite / {
        to {path} {path}/ /index.php?{query}

tls / {
        protocols tls1.2
        curves p384
        key_type p384

A guide for journalists that need to defend their work from governments

This is a brain dump guide that may be expanded depending on feedback. The goal is for a journalist to be able to keep all of their work “in the cloud” (on a personally controlled Tor hidden service) and not keep any sensitive data with them when they travel.

Most journalists are not able to commit to the requirements (high technical competency) of this guide. Dedicated journalists should consult with a technical expert that they trust.

Skeleton guide

1. Find some secure and stable places to host a few Tor hidden service web hosts. Choose different places that small actors (non-targeted malice, etc) and big actors (targeting by private companies, local law enforcement searches, intelligence agencies, etc) would have trouble finding. Inexpensive “netbooks” are great options because they are portable, inconspicuous, have built in batteries, surge protectors, and RAM that’s integrated into the system board (can’t be removed for evil maid attacks). Always presume adversaries will find them, so always use layered security.

1.1. The web hosts must at least be behind a basic firewall, even a residential Internet connection using NAT. The host itself must not have any externally-accessible ports, must employ LUKS disk encryption, and must be running the most current version of Tor. OwnCloud Community edition or WordPress are ideal platforms that allow remote uploading of files or note taking. Access to the web resource must be hardened and password protected in case a random adversary is able to uncover the hidden service address.

1.2. Physical access to the host, and remote access to the host, must require high-entropy passwords. You must remember them, or you must have a secure way of documenting and retrieving them like disposable SpiderOak accounts. Never carry passwords or your hidden service addresses with you. Two-factor authentication options cannot be used because you must presume said authentication devices will be taken from you.

1.3. Hosting multiple Tor hidden service web servers, for redundancy, can be configured to automatically sync with each other via Tor.

2. Carry the following:

2.1. An inexpensive laptop that has an unencrypted hard drive and operating system that has some “use” (don’t ever use it). Some passive-search actors will force you to turn on the device to demonstrate its legitimacy. Turning it on and opening apps keeps the friction low between you and your adversary. Always presume that adversaries will take it all from you. If any device is taken from you and removed from your sight, dispose of the device immediately.

2.2. Several USB drives with Tails Linux, and in different locations (on-person necklace, pockets, bags). If you’re able to maintain ownership of at least one, and you trust it was not tampered with, you won’t need to create a new one after passing through security check points.

2.2.1. Presume that the drives will be confiscated, so you must know how to recreate a Tails drive from any operating system. Don’t use pre-created drives if you think leaving them behind in “secure” places will help you. And don’t mail pre-created drives to yourself, they are trivially intercepted. Plan ahead to determine where you can create a new Tails drive, such as a retail electronics store or a local library. If you can’t assure that your Tails drive is clean, and your computer is free from any hardware or firmware compromise, do not access your Tor hidden service resources.

Hardening WordPress

Published: 2015-Oct-23
Updated: 2016-May-11

Applications configured

Ubuntu Server 14.04 – 16.04
Apache 2.4.20
MySQL 5.6.28
PHP 7.0.5
OpenSSL 1.0.2g
WordPress 4.5
Sendmail 8.14.9
OSSEC 2.8.3
apt-transport-tor 0.2.1
Let’s Encrypt

The TLS configuration in this article will get you a Qualys SSL Labs perfect A+ (100/100/100/100).


Things that should be in this guide but I still haven’t learned well:

— HPKP (key pinning, it still scares me)
— DNSSEC (my registrar doesn’t support it, serious wtf)
— Grsecurity kernel patches
— Torify Sendmail

Regularly test your transport security here:

Regularly test your security headers here:


sudo vim /etc/ssh/sshd_config

Comment out these lines:

#HostKey /etc/ssh/ssh_host_dsa_key
#HostKey /etc/ssh/ssh_host_ecdsa_key

Edit these lines:

ServerKeyBits 4096
LoginGraceTime 30
PermitRootLogin no

Add these lines:




Done. Then:

sudo service ssh restart


sudo ufw limit 22/tcp
sudo ufw allow 443/tcp
sudo ufw allow out 22/tcp
sudo ufw allow out 25/tcp
sudo ufw allow out 53/udp
sudo ufw allow out 443/tcp
sudo ufw allow out 9050/tcp
sudo ufw deny out to any
sudo ufw enable
sudo ufw status verbose

Or in one line…

sudo ufw limit 22/tcp && sudo ufw allow 443/tcp && sudo ufw allow out 22/tcp && sudo ufw allow out 25/tcp && sudo ufw allow out 53/udp && sudo ufw allow out 443/tcp && sudo ufw allow out 9050/tcp && sudo ufw deny out to any && sudo ufw enable && sudo ufw status verbose

This UFW (iptables) rule set makes it so brute forcing SSH won’t work and allows all HTTPS traffic. These rules also make it so no traffic can leave the web server unless it is SSH (22), SMTP (25), DNS (53), HTTPS (443), or Tor Socks (9050) traffic. Most web servers do not go as far as block all outbound traffic by default, but it is important in case the web server does become compromised.

I would usually allow outbound HTTP traffic because the Ubuntu update repositories require HTTP… they’re not HTTPS which is extremely sad. However, we will be Torifying Apt so that’s why we allow outbound 9050/tcp. If you don’t want to Torify Apt, you’ll need to allow outbound 80/tcp instead of 9050/tcp.

I allow outbound SMTP traffic because I use Sendmail to send TLS encrypted email notifications.

I do not allow any inbound HTTP traffic, and it is because of HSTS Preload. All major browsers know to connect to my website via HTTPS and not HTTP.

Patch (securely)

Make sure your OS is current then restart:

sudo apt-get update

sudo apt-get dist-upgrade

sudo shutdown -r now

Get updates over Tor instead of HTTP. First install and configure Tor.

sudo vim /etc/apt/sources.list

Add these lines to the bottom:

deb wily main


sudo gpg --keyserver --recv 886DDD89

sudo gpg --export A3C4F0F979CAA22CDBA8F512EE8CBC9E886DDD89 | sudo apt-key add -

sudo apt-get install tor apt-transport-tor -V

Backup sources.list:

sudo cp /etc/apt/sources.list /etc/apt/sources.bak

Edit sources.list with tor+http:

sudo vim /etc/apt/sources.list

For example (add “tor+” to all active lines):

deb tor+ wily main restricted

Add these PPAs so that Apache, MySQL and PHP will be up to date:

sudo add-apt-repository ppa:ondrej/apache2

sudo add-apt-repository ppa:ondrej/mysql-5.6

sudo LC_ALL=C.UTF-8 add-apt-repository ppa:ondrej/php

Be sure to Torify the new PPAs.

sudo vim /etc/apt/sources.list.d/ondrej-ubuntu-apache2-wily.list
deb tor+ wily main


sudo vim /etc/apt/sources.list.d/ondrej-ubuntu-mysql-5_6-wily.list
deb tor+ wily main


sudo vim /etc/apt/sources.list.d/ondrej-ubuntu-php-wily.list
deb tor+ wily main

Done. Then:

sudo apt-get update

All repositories should now read tor+http://… because they are routed through Tor. If any hang, it is likely because a repo was missed (and needs tor+) and is trying to connect using outbound HTTP port 80.

Now install the apps:

sudo apt-get update && sudo apt-get install apache2 libapache2-mod-evasive unzip nghttp2 nghttp2-server mysql-server-5.6 php7.0 sendmail -V

Sendmail config

sudo vim /etc/mail/

Edit or add these lines:


This configuration assures that TLS will be used when sending mail to a destination mail server (not STARTTLS!). However, you’d still be using self-signed certs. Regardless, ECDH+AES256 is the only cipher being set because I know my email server receiving my alerts will use it.

sudo service sendmail restart

I can verify the TLS encryption in my received email headers:

Received: from ( [])
(version=TLS1_2 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256/256);

After you install Let’s Encrypt certs, you may want to add these lines:

O ServerCertFile=/etc/letsencrypt/live/
O ServerKeyFile=/etc/letsencrypt/live/
O DHParameters=/etc/ssl/private/dhparams_4096.dh.param

This would configure Sendmail to use the same certs that you will use for HTTPS, which is fine since it should be from the same TLD. However, if you have a mail server with valid certs already, you should use those certs instead here (like if your mail server certs are for


vim /etc/php/7.0/apache2/php.ini

Uncomment and edit this line:

sendmail_path = /usr/sbin/sendmail -t -i


sudo service apache2 restart

Apache config

sudo vim /etc/apache2/mods-available/evasive.conf

Uncomment the first 6 lines. Change if needed. Edit the DOSEmailNotify line if you’d like to be alerted when a specific IP address gets blocked. Then:

sudo vim /etc/apache2/apache2.conf

Add these lines:

FileETag None
Header unset ETag
AuthnCacheSOCache shmcb

Edit this line:

Timeout 30

Done. Then:

sudo a2enmod headers http2 ssl socache_shmcb

Done. Then:

sudo vim /etc/apache2/conf-available/security.conf

Uncomment these lines:

Directory /
   AllowOverride None
   Require all denied

Add these lines:

Header always set X-XSS-Protection: "1; mode=block"
Header always set X-Permitted-Cross-Domain-Policies: "master-only"
Header always set Cache-Control: "private, no-cache, no-store, must-revalidate"
Header always set Pragma: "no-cache"
Header always set Expires: "-1"
Header always set X-Content-Type-Options: "nosniff"
Header always set X-Frame-Options: "sameorigin"
Header always set Content-Security-Policy: "default-src 'self'"

Edit these lines:

ServerTokens Prod
ServerSignature Off
TraceEnable Off

Done. Then:

sudo service apache2 restart

Apache TLS config

Start by Generating 4096-bit DHparams. This will take a while (5+ minutes) depending on your server’s processing capabilities. These three lines may be something worth scripting to recreate every month via cron:

sudo openssl dhparam -out /etc/ssl/private/dhparams_4096.tmp 4096

sudo mv /etc/ssl/private/dhparams_4096.tmp  /etc/ssl/private/dhparams_4096.pem

sudo cp /etc/ssl/private/dhparams_4096.pem /etc/ssl/private/dhparams_4096.dh.param

Done. Then:

sudo vim /etc/apache2/mods-enabled/ssl.conf

Add these lines:

SSLCompression off
SSLSessionTickets off
SSLUseStapling on
SSLStaplingResponderTimeout 5
SSLStaplingReturnResponderErrors off
SSLStaplingCache shmcb:/var/run/ocsp(128000)
SSLOpenSSLConfCmd DHParameters /etc/ssl/private/dhparams_4096.pem
SSLOpenSSLConfCmd Curves secp384r1

Change these lines:

SSLRandomSeed startup file:/dev/urandom 4096
SSLRandomSeed connect file:/dev/urandom 4096

Uncomment and/or edit these lines:

SSLHonorCipherOrder on
SSLProtocol -all +TLSv1.2

Let’s Encrypt will modify your Apache default-ssl.conf file for you. After installing Let’s Encrypt, I made sure to create 4096-bit keys:

sudo ./letsencrypt-auto --apache -d -d --rsa-key-size 4096

Keep an eye on ECDSA key support.

Done. Then:

sudo vim /etc/apache2/sites-available/default-ssl.conf

Add these lines:

SSLEngine on
Header edit Set-Cookie ^(.*)$ $1;Secure;SameSite=Strict
Header always set Strict-Transport-Security "max-age=15768000; includeSubDomains; preload"
Header always set Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'"

Add these lines under “/Directory” but before “/VirtualHost”:

#Protocols h2 http/1.1
H2Direct on
H2MaxSessionStreams 20
H2MaxWorkerIdleSeconds 20
H2MaxWorkers 20
H2MinWorkers 10
H2ModernTLSOnly on
H2SerializeHeaders off
H2SessionExtraFiles 10
H2StreamMaxMemSize 128000
H2TLSCoolDownSecs 0
H2TLSWarmUpSize 0
H2Upgrade on
H2WindowSize 128000

Now that HSTS and HSTS Preload are configured, submit your domain to Google for getting added to the Chrome HSTS Preload list:

Doing so will eventually replicate to all other mainstream browsers. My experieince was that after submitting my domain for Preload, I had to wait a couple of weeks for it to show up in Chrome, and a couple more weeks to show up in Firefox. Tor Browser took the longest. Just be patient.

I keep the “Protocols h2 http/1.1” directive commented out because I can’t achieve a perfect Qualys SSL Labs score and force HTTP2. When I do, Chrome sometimes doesn’t load the website because the HTTP2 specification doesn’t accept some of the cipher suites with this configuration.

Also check out HTTP2 Explained.

Done. Then:

sudo a2ensite default-ssl.conf
sudo a2dissite 000-default.conf
sudo service apache2 restart

Basic MySQL hardening

sudo apt-get install mysqltuner
sudo mysqloptimize -p -u root -A -o
sudo mysqltuner

Make any of the recommended changes (then re-run mysqltuner):

sudo vim /etc/mysql/mysql.conf.d/mysqld.cnf


sudo vim /var/www/

Add these lines:

define('WP_MEMORY_LIMIT', '2G');
define('WP_MAX_MEMORY_LIMIT', '2G');

The WP_MAX_MEMORY_LIMIT specifically addresses the administrative backend, which has a different memory limit from both WordPress and PHP configs.

Then, disable the HTML5 canvas data issue (noticeable in Tor Browser):


(thanks Wilton)

wp-admin > Appearance > Editor > functions.php

Add this to the bottom:

remove_action( 'wp_head', 'print_emoji_detection_script', 7 );

remove_action( 'admin_print_scripts', 'print_emoji_detection_script' );

remove_action( 'wp_print_styles', 'print_emoji_styles' );

remove_action( 'admin_print_styles', 'print_emoji_styles' );

OSSEC and Sucuri

OSSEC is a powerful Host-based Intrusion Detection System (HIDS). Sucuri is a powerful WordPress plugin. This section gets both installed and gets them working together.

wget -U ossec

tar -zxvf ossec-hids-*.tar.gz

cd ossec-hids-2.8.3/

sudo ./

During installation:

1- local
2- accept default
3- accept all defaults


sudo /var/ossec/bin/ossec-control start

sudo vim /var/ossec/etc/ossec.conf

Verify/change/add these lines:

email_notification yes /email_notification
email_to /email_to
smtp_server localhost /smtp_server
email_from /email_from

This configuration should force the use of TLS encrypted emails if Sendmail is installed and configured.

sudo service ossec restart

Then, in the WordPress Dashboard, install and activate the “Sucuri Security” plugin.

Open the Sucuri Security dashboard and generate an API key. It will use your WordPress admin account/email. I had to manually email the provided email to get a key.


sudo touch /var/log/wordpress.log

sudo chown www-data:www-data /var/log/wordpress.log

In WordPress Dashboard, navigate to Sucuri Security > Settings > Log Exporter

Enter “/var/log/wordpress.log” and save.


sudo vim /var/ossec/etc/ossec.conf

Under “Files to monitor (localfiles)“, add these lines:

    log_format syslog /log_format
    location /var/log/wordpress.log /location
sudo service ossec restart

Be prepared for lots of email notifications, thankfully TLS encrypted. Be careful about what information is copied and stored by your email provider.

WordPress Plugins

— Disable Comments
— Disable Google Fonts
— Disable XML-RPC

Installing MariaDB 10 on Ubuntu 13.04

Upon installing Ubuntu 13.04 x64 server onto new hardware and transferring my content to the new host, MySQL 5.5 was giving me too many service-blocking problems. I’m also having SSL cert problems with, so I’m using again for my WordPress blog.

MariaDB is supposed to have official Raring repositories out soon, but you can use the repos for 12.10 until this without issue:

sudo apt-get install software-properties-common sudo apt-key adv –recv-keys –keyserver 0xcbcb082a1bb943db sudo add-apt-repository ‘deb quantal main’

sudo apt-get update sudo apt-get install mariadb-server


Editing WordPress’ BuddyPress image header

Researching how to do this took way to long–these are simple notes to help others. My goal was to use a taller image, in the header, in the default BuddyPress theme. Below is what worked for me–adjust accordingly.

EDIT: (line 156)



 padding-top: 25px;


 padding-top: 250px;

EDIT: (line 94)



 define( 'HEADER_IMAGE_HEIGHT', apply_filters( 'bp_dtheme_header_image_height', 133 ) );


 define( 'HEADER_IMAGE_HEIGHT', apply_filters( 'bp_dtheme_header_image_height', 363 ) );

And that’s it! Cheers