WordPress below Nextcloud

We will install WordPress below (aka in a subfolder) Nextcloud and integrate this WordPress blog as an external site within your Nextcloud. This guide is based on the the Nextcloud installation guide and as well on the GeoIP guide and will additionally secure a bit using WP fail2ban plugin and by moving the wp-config.php out of the WordPress webdirectory.

sudo -s

Assuming your blog should be reachable via https://your.dedyn.io/wordpress we will amend the vhost nextcloud.conf accordingly:

cd /etc/nginx/conf.d
cp nextcloud.conf nextcloud.conf.bak
nano /etc/nginx/conf.d/nextcloud.conf
server {
server_name your.dedyn.io;
listen 80 default_server;
listen [::]:80 default_server;
include /etc/nginx/ipblocker;
# if geoip isn't enabled please remove the 'include ...' row
location ^~ /.well-known/acme-challenge {
proxy_pass http://127.0.0.1:81;
proxy_set_header Host $host;
}
location / {
return 301 https://$host$request_uri;
}
}
server {
server_name your.dedyn.io;
listen 443 ssl http2 default_server;
listen [::]:443 ssl http2 default_server;
include /etc/nginx/ipblocker;
# if geoip isn't enabled please remove the 'include ...' row
root /var/www/nextcloud/;
location = /robots.txt {
allow all;
log_not_found off;
access_log off;
}
location = /.well-known/carddav {
return 301 $scheme://$host/remote.php/dav;
}
location = /.well-known/caldav {
return 301 $scheme://$host/remote.php/dav;
}
client_max_body_size 10240M;
### NEW: WORDPRESS ###
location ^~ /wordpress/ {
client_max_body_size 1024M;
proxy_buffering off;
proxy_connect_timeout 3600;
proxy_max_temp_file_size 1024M;
proxy_pass http://127.0.0.1:84;
proxy_read_timeout 3600;
proxy_redirect off;
proxy_request_buffering off;
proxy_send_timeout 3600;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
send_timeout 3600;
}
### END: WORDPRESS ###
location / {
rewrite ^ /index.php$request_uri;
}
location ~ ^/(?:build|tests|config|lib|3rdparty|templates|data)/ {
deny all;
}
location ~ ^/(?:\.|autotest|occ|issue|indie|db_|console) {
deny all;
}
location ~ \.(?:flv|mp4|mov|m4a)$ {
mp4;
mp4_buffer_size 100M;
mp4_max_buffer_size 1024M;
fastcgi_split_path_info ^(.+?.php)(\/.*|)$;
include fastcgi_params; include php_optimization.conf;
fastcgi_pass php-handler; fastcgi_param HTTPS on;
}
location ~ ^\/(?:index|xphp|remote|public|cron|core\/ajax\/update|status|ocs\/v[12]|updater\/.+|oc[ms]-provider\/.+).php(?:$|\/) {
fastcgi_split_path_info ^(.+?.php)(\/.*|)$;
include fastcgi_params;
include php_optimization.conf;
fastcgi_pass php-handler;
fastcgi_param HTTPS on;
}
location ~ ^\/(?:updater|oc[ms]-provider)(?:$|\/) {
try_files $uri/ =404;
index index.php;
}
location ~ .(?:css|js|woff2?|svg|gif|map|png|html|ttf|ico|jpg|jpeg)$ {
try_files $uri /index.php$request_uri;
access_log off;
expires 360d;
}
}

Create a new vhost file in particular to your new WordPress:

sudo -u www-data mkdir -p /var/www/wordpress
nano /etc/nginx/conf.d/wordpress.conf
server {
server_name 127.0.0.1;
listen 127.0.0.1:84;
root /var/www/;
location ^~ /wordpress { 
index index.php;
location /wordpress {
try_files $uri $uri/ /wordpress/index.php$is_args$args;
}
location = /wordpress/favicon.ico {
log_not_found off;
access_log off;
}
location = /wordpress/robots.txt {
allow all;
log_not_found off;
access_log off;
}
location ~ \.php$ {
try_files $uri =404;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
include fastcgi_params; 
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
fastcgi_pass php-handler;
fastcgi_connect_timeout 60;
fastcgi_index index.php;
fastcgi_param REMOTE_ADDR $http_x_real_ip;
}
location ~* /wordpress/\.(js|css|png|jpg|jpeg|gif|ico)$ {
expires max;
log_not_found off;
}
location /wordpress/wp-admin {
auth_basic "Restricted Area";
auth_basic_user_file /etc/nginx/wordpress-access;
}
location ~* /(?:uploads|files)/.*.(html|htm|shtml|php|js|swf)$ {
deny all;
}
}
}

Harden your wp-admin a bit and create a password validation file:

apt install apache2-utils -y
htpasswd -c /etc/nginx/wordpress-access Max.Musterfrau
htpasswd -c /etc/nginx/wordpress-access a.b

From now the acces to wp-admin will be monitored by fail2ban (fail2ban: nginx-http-auth). Test your web server configuration

nginx -t

and if it’s ok, restart nginx now

service nginx restart

Download and extract the latest Worpress sources

curl https://wordpress.org/latest.tar.gz | tar xz -C /var/www/wordpress --strip-components=1

and apply the proper permissions:

chown -R www-data:www-data /var/www/wordpress

Create the WordPress database

mysql -uroot -p
CREATE DATABASE wordpress CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
CREATE USER wordpress@localhost identified by 'wordpressDBpassword';
GRANT ALL PRIVILEGES on wordpress.* to wordpress@localhost;
flush privileges;
quit;

and create the WordPress configuration file (wp-config.php) with your own salts:

sudo -u www-data nano /var/www/wordpress/wp-config.php
<?php
if (strpos($_SERVER['HTTP_X_FORWARDED_PROTO'], 'https') !== false)
    $_SERVER['HTTPS']='on';
if(isset($_SERVER['HTTP_X_FORWARDED_HOST']))
    $_SERVER['HTTP_HOST'] = $_SERVER['HTTP_X_FORWARDED_HOST'];
define('WP_HOME', 'https://your.dedyn.io/wordpress');
define('WP_SITEURL', 'https://your.dedyn.io/wordpress');
define('DB_NAME', 'wordpress');
define('DB_USER', 'wordpress');
define('DB_PASSWORD', 'wordpressDBpassword');
define('DB_HOST', 'localhost');
define('DB_CHARSET', 'utf8mb4');
define('DB_COLLATE', '');
define('AUTH_KEY',         'from the url below');
define('SECURE_AUTH_KEY',  'from the url below');
define('LOGGED_IN_KEY',    'from the url below');
define('NONCE_KEY',        'from the url below');
define('AUTH_SALT',        'from the url below');
define('SECURE_AUTH_SALT', 'from the url below');
define('LOGGED_IN_SALT',   'from the url below');
define('NONCE_SALT',       'from the url below');
$table_prefix = 'ncblog_';
define('WP_DEBUG', false);
if ( !defined('ABSPATH') )
 define('ABSPATH', dirname(__FILE__) . '/');
require_once(ABSPATH . 'wp-settings.php');

To generate your define-values open: https://api.wordpress.org/secret-key/1.1/salt/

Call your WordPress instance in your preferred browser (https://your.dedyn.io/wordpress) and run the installation wizard


Logon to your Nextcloud and switch over as Nextcloud admin to the External Sites configuration panel:

Create a new External site entry (https://your.dedyn.io/wordpress)

Nextcloud app: External sites

and your WordPress appears as an app (link) within your Nextcloud.

Example: WordPress within Nextcloud

For security reasons move your wp-config.php out of the WordPress webdirectory and create a new wp-config.php that only points to the origin one:

cd /var/www/wordpress
mv wp-config.php /etc/nginx
nano wp-config.php
<?php
include('/etc/nginx/wp-config.php');
chown www-data:www-data /etc/nginx/wp-config.php /var/www/wordpress/wp-config.php
chmod 400 /var/www/wordpress/wp-config.php

Finally harden your nginx webserver using WP-fail2ban for WordPress logins either. Activate the plugin

WP fail2ban plugin

called “WP fail2ban” from Charles Lecklider and create the fail2ban-filter and jail files.

nano /etc/fail2ban/filter.d/wordpress-soft.conf
# Fail2Ban filter for WordPress soft failures
# Auto-generated: 2019-04-18T14:45:30+00:00
#
[INCLUDES]
before = common.conf
[Definition]
_daemon = (?:wordpress|wp)
failregex = ^%(__prefix_line)sAuthentication failure for .* from <HOST>$
            ^%(__prefix_line)sREST authentication failure for .* from <HOST>$
            ^%(__prefix_line)sXML-RPC authentication failure for .* from <HOST>$
ignoreregex =
# DEV Notes:
# Requires the 'WP fail2ban' plugin:
# https://wp-fail2ban.com/
#
# Author: Charles Lecklider
nano /etc/fail2ban/filter.d/wordpress-hard.conf
# Fail2Ban filter for WordPress hard failures
# Auto-generated: 2019-04-18T14:45:30+00:00
#
[INCLUDES]
before = common.conf
[Definition]
_daemon = (?:wordpress|wp)
failregex = ^%(__prefix_line)sAuthentication attempt for unknown user .* from <HOST>$
            ^%(__prefix_line)sREST authentication attempt for unknown user .* from <HOST>$
            ^%(__prefix_line)sXML-RPC authentication attempt for unknown user .* from <HOST>$
            ^%(__prefix_line)sSpam comment \d+ from <HOST>$
            ^%(__prefix_line)sBlocked user enumeration attempt from <HOST>$
            ^%(__prefix_line)sBlocked authentication attempt for .* from <HOST>$
            ^%(__prefix_line)sXML-RPC multicall authentication failure from <HOST>$
            ^%(__prefix_line)sPingback error .* generated from <HOST>$
ignoreregex =
# DEV Notes:
# Requires the 'WP fail2ban' plugin:
# https://wp-fail2ban.com/
#
# Author: Charles Lecklider
nano /etc/fail2ban/filter.d/wordpress.local
[wordpress-hard]
backend = auto
enabled = true
filter = wordpress-hard
logpath = /var/log/auth.log
maxretry = 1
port = http,https
bantime = 1800
findtime = 3600 

[wordpress-soft]
backend = auto
enabled = true
filter = wordpress-soft
logpath = /var/log/auth.log
maxretry = 3
port = http,https
bantime = 1800
findtime = 3600 

Restart fail2ban

service fail2ban restart

and review your new fail2ban filter issuing

fail2ban-client status nextcloud && fail2ban-client status nginx-http-auth && fail2ban-client status wordpress-hard && fail2ban-client status wordpress-soft && fail2ban-client status sshd
fail2ban status

I do strongly recommend to create a new WordPress Admin and to purge the default one. Additionally i do strongly recommend a second factor e.g. Google Authenticator to increase the WordPress security.

Recommended WordPress plugins

If you connect to the wp-admin page directly, don’t forget the ending slash:
https://your.dedyn.io/wordpress/wp-admin/
otherwise the site remains blank!

Enjoy your harden WordPress, but don’t forget to backup your WordPress. Find instructions here: Nextcloud backup and restore

Carsten Rieger

Carsten Rieger is a senior system engineer in full-time and also working as an IT freelancer. He is working with linux environments for more than 13 years, an Open Source enthusiast and highly motivated on linux installation and troubleshooting. Mostly working with Debian/Ubuntu Linux, Nginx and Apache web server, MariaDB/MySQL/PostgreSQL, PHP, Cloud infrastructure (e.g. Nextcloud) and other open source projects (e.g. Roundcube) and in voluntary work for the Dr. Michael & Angela Jacobi Stiftung for more than 7 years.