WordPress Installation

From WikiMLT

Word­Press in­stal­la­tion with­in Ubun­tu 20.04 (or lat­er), Apache 2.4 and PHP 7.4. This post is based on my an­swer of the ques­tion Word­Press In­stal­la­tion Failed at Ask Ubun­tu, but cov­ers on­ly the gen­er­al line.

Here we as­sume there is a FQDN which points to the servers pub­lic IP ad­dress and there is in­stalled Apache2 that lis­ten to ports 80 and 443. As il­lus­tra­tional ex­am­ple for that FQDN we will use wp​.example​.com.

Down­load the lat­est Word­Press re­lease

cd /var/www/
sudo wget https://wordpress.org/latest.tar.gz && \
sudo tar -xvzf latest.tar.gz && \
sudo rm ./latest.tar.gz*
sudo mv wordpress wp.example.com
sudo chown -R www-data:www-data wp.example.com/
sudo -u www-data mkdir wp.example.com/wp-content/uploads
sudo -u www-data cp html/favicon.ico wp.example.com/
  • Change the name of the doc­u­ment root di­rec­to­ry from /var/www/wp.example.com to the name of the ac­tu­al di­rec­to­ry in use.

Cre­ate MySQL Data­base for the new Word­Press in­stance

For Mari­aDB read the ar­ti­cle: Mi­grate MySQL to Mari­aDB.

sudo mysql
CREATE DATABASE wp_example_com;
CREATE USER 'wp_example_com_user'@'localhost' IDENTIFIED WITH mysql_native_password BY '<your-strong-password>';
GRANT ALL PRIVILEGES ON wp_example_com.* TO 'wp_example_com_user'@'localhost' WITH GRANT OPTION;
FLUSH PRIVILEGES;
exit
  • Re­place wp_​​​example_​​​com with the ac­tu­al data­base name in use.
  • Re­place wp_​​​example_​​​com_​​​user with the ac­tu­al data­base user­name in use.
  • Re­place <your-strong-pass­word> with an ac­tu­al strong pass­word.

Cre­ate LetsEn­crypt cer­tifi­cate for the new Word­Press in­stance

Dis­able Mod­Se­cu­ri­ty (it it is en­abled) for a while. It could block Let's en­crypt au­then­ti­ca­tion en­gine when there are not ap­pro­pri­ate ex­cep­tion rules.

sudo a2dismod security2 && sudo systemctl restart apache2.service

Gen­er­ate a cer­tifi­cate for wp​.example​.com – re­place it with the ac­tu­al FQDN in use (you could add the op­tion –rsa-key-size 4096 if you think this could bring more se­cu­ri­ty).

sudo letsencrypt certonly --apache --email admin@wp.example.com -d wp.example.com

Al­ter­na­tive­ly you could cre­ate wild­card cer­ti­fi­cat by a com­mand as the fol­low.

sudo letsencrypt certonly --email admin@example --apache \
--preferred-challenges=dns -d example -d *.example

En­able Mod­Se­cu­ri­ty – it is not pre­sent­ed you could do sys­tem­ctl re­load apache2.service in­stead restart­ing it.

sudo a2enmod security2 && sudo systemctl restart apache2.service

More com­mands.

#Cer­ert­bot man­age­ment
  • List of all cer­tifi­cates:
sudo letsencrypt certificates
  • Re­move a cer­tifi­cate:
sudo letsencrypt delete --cert-name example.com
  • Re­move all cer­tifi­cates:
sudo letsencrypt delete

Cre­ate Apache2 Vir­tu­al Host for the new Word­Press in­stance

sudo nano /etc/apache2/sites-available/wp.example.com.conf
<VirtualHost *:80>
	ServerName wp.example.com
	ServerAlias www.wp.example.com

	ServerAdmin support@wp.example.com

    # Redirect Requests to HTTPS
	Redirect permanent "/" "https://wp.example.com/"

    ErrorLog ${APACHE_LOG_DIR}/wp.example.com.error.log
    CustomLog ${APACHE_LOG_DIR}/wp.example.com.access.log combined
</VirtualHost>

<IfModule mod_ssl.c>
<VirtualHost _default_:443>
	ServerName wp.example.com
	ServerAlias www.wp.example.com
	ServerAdmin support@wp.example.com

    <IfModule mod_rewrite.c>
        RewriteEngine On
        RewriteCond %{HTTP_HOST} www.wp.example.com$ [NC]
        RewriteRule ^(.*)$ https://wp.example.com/$1 [R=301,NC,L]
    </IfModule>

    ErrorLog ${APACHE_LOG_DIR}/wp.example.com.error.log
    CustomLog ${APACHE_LOG_DIR}/wp.example.com.access.log combined

	SSLEngine on
	SSLCertificateFile /etc/letsencrypt/live/wp.example.com/cert.pem
	SSLCertificateKeyFile /etc/letsencrypt/live/wp.example.com/privkey.pem
	SSLCertificateChainFile /etc/letsencrypt/live/wp.example.com/chain.pem
	#SSLCertificateChainFile /etc/letsencrypt/live/wp.example.com/fullchain.pem

    <IfModule pagespeed_module>
        # For the dev stage
        ModPagespeed off
    </IfModule>

    Define newIinstanceWP_DocumentRoot "/var/www/wp.example.com"
    DocumentRoot "${newIinstanceWP_DocumentRoot}"
	<Directory "${newIinstanceWP_DocumentRoot}">
		DirectoryIndex index.php
		Options None FollowSymLinks
		Require all granted
		
		#AllowOverride None
		AllowOverride All

		<IfModule security2_module>
		    # For the dev stage
			SecRuleEngine Off
		</IfModule>
	</Directory>

    <Location />
		# For the dev stage
        #Require ip 172.16.1.0/24
        #Require ip 192.168.1.0/24
    </Location>

    <LocationMatch "/wp-admin/admin-ajax.php">
		<IfModule security2_module>
            SecRequestBodyNoFilesLimit 1310720
			SecRequestBodyInMemoryLimit 1310720
        </IfModule>
    </LocationMatch>

    <LocationMatch "/wp-admin/post.php">
        <IfModule security2_module>
			SecRequestBodyLimit 131072000
			SecRequestBodyNoFilesLimit 1310720
			SecRequestBodyInMemoryLimit 1310720
        </IfModule>
    </LocationMatch>


    # The following section is according to this scarnnr response:
    # https://securityheaders.com/?q=szs.space&followRedirects=on

    # https://scotthelme.co.uk/hsts-the-missing-link-in-tls/
    Header always set Strict-Transport-Security "max-age=63072000; includeSubDomains"

    # https://scotthelme.co.uk/content-security-policy-an-introduction/
    # https://content-security-policy.com/
    # https://www.mediawiki.org/wiki/Requests_for_comment/Content-Security-Policy
    #Header set Content-Security-Policy "default-src 'self'"
    #Header set Content-Security-Policy "script-src 'self'"
    #Header set Content-Security-Policy "default-src https://"

    # https://scotthelme.co.uk/a-new-security-header-referrer-policy/
    #Header always set Referrer-Policy "same-origin"

    #Header set Access-Control-Allow-Origin "*"
    #Header set X-Frame-Options "allow-from youtube.com"

    # https://scotthelme.co.uk/a-new-security-header-feature-policy/
    Header always set Feature-Policy "microphone 'none'; payment 'none'; sync-xhr 'self' https://wp.example.com"
</VirtualHost>
</IfModule>
  • Re­place the name of the con­fig­u­ra­tion file from wp.example.com.conf to an ac­tu­al name.
  • Re­place wp​.example​.com and www​.wp​.example​.com with the ac­tu­al FQDNs in use.
  • Re­place the name of the vari­able newIinstanceWP_​​​DocumentRoot with some­thing else.
  • Re­place the name of the doc­u­ment root di­rec­to­ry from /var/www/wp.example.com.wp to the name of the ac­tu­al di­rec­to­ry in use.
sudo a2ensite wp.example.com.conf
sudo systemctl restart apache2.service

In ad­di­tion you cold tweak the cache pol­i­cy by adding the fol­low­ing code in­to the .htaac­ctes file of the in­stance (by us­ing an ex­ten­sion or man­u­al­ly) or in­to the vir­tu­al host con­fig­u­ra­tion file.

sudo nano /var/www/wp.example.com/.htaccess
# BEGIN WordPress
# The directives (lines) between "BEGIN WordPress" and "END WordPress" are
# dynamically generated, and should only be modified via WordPress filters.
# Any changes to the directives between these markers will be overwritten.
<IfModule mod_rewrite.c>
    RewriteEngine On
    RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
    RewriteBase /
    RewriteRule ^index\.php$ - [L]
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteRule . /index.php [L]
</IfModule>
# END WordPress

# Compress content before it is delivered to the client
# https://httpd.apache.org/docs/current/mod/mod_deflate.html
<IfModule mod_deflate.c>
    # Compress HTML, CSS, JavaScript, Text, XML and fonts
    AddOutputFilterByType DEFLATE application/javascript
    AddOutputFilterByType DEFLATE application/rss+xml
    AddOutputFilterByType DEFLATE application/vnd.ms-fontobject
    AddOutputFilterByType DEFLATE application/x-font
    AddOutputFilterByType DEFLATE application/x-font-opentype
    AddOutputFilterByType DEFLATE application/x-font-otf
    AddOutputFilterByType DEFLATE application/x-font-truetype
    AddOutputFilterByType DEFLATE application/x-font-ttf
    AddOutputFilterByType DEFLATE application/x-javascript
    AddOutputFilterByType DEFLATE application/xhtml+xml
    AddOutputFilterByType DEFLATE application/xml
    AddOutputFilterByType DEFLATE font/opentype
    AddOutputFilterByType DEFLATE font/otf
    AddOutputFilterByType DEFLATE font/ttf
    AddOutputFilterByType DEFLATE image/svg+xml
    AddOutputFilterByType DEFLATE image/x-icon
    AddOutputFilterByType DEFLATE text/css
    AddOutputFilterByType DEFLATE text/html
    AddOutputFilterByType DEFLATE text/javascript
    AddOutputFilterByType DEFLATE text/plain
    AddOutputFilterByType DEFLATE text/xml

    # Remove browser bugs (only needed for really old browsers)
    BrowserMatch ^Mozilla/4 gzip-only-text/html
    BrowserMatch ^Mozilla/4\.0[678] no-gzip
    BrowserMatch \bMSIE !no-gzip !gzip-only-text/html
    Header append Vary User-Agent
</IfModule>

# BEGIN CACHE POLICY
# Source: the default settings of WP-HUMMINGBIRD-CACHING
<IfModule mod_headers.c>
    Header set Cache-Control "max-age=31536000"

    <FilesMatch "\.(txt|xml|js)$">
        Header set Cache-Control "max-age=31536000"
    </FilesMatch>

    <FilesMatch "\.(css)$">
        Header set Cache-Control "max-age=31536000"
    </FilesMatch>

    <FilesMatch "\.(flv|ico|pdf|avi|mov|ppt|doc|mp3|wmv|wav|mp4|m4v|ogg|webm|aac|eot|ttf|otf|woff|woff2|svg)$">
        Header set Cache-Control "max-age=31536000"
    </FilesMatch>

    <FilesMatch "\.(jpg|jpeg|png|gif|swf|webp)$">
        Header set Cache-Control "max-age=31536000"
    </FilesMatch>
</IfModule>

<IfModule mod_expires.c>
    ExpiresActive On
    #ExpiresDefault A0
    ExpiresDefault A31536000

    <FilesMatch "\.(txt|xml|js)$">
        ExpiresDefault A31536000
    </FilesMatch>

    <FilesMatch "\.(css)$">
        ExpiresDefault A31536000
    </FilesMatch>

    <FilesMatch "\.(flv|ico|pdf|avi|mov|ppt|doc|mp3|wmv|wav|mp4|m4v|ogg|webm|aac|eot|ttf|otf|woff|woff2|svg)$">
        ExpiresDefault A31536000
    </FilesMatch>

    <FilesMatch "\.(jpg|jpeg|png|gif|swf|webp)$">
        ExpiresDefault A31536000
    </FilesMatch>
</IfModule>

# An alternative of the above
<IfModule mod_expires.c>
    ExpiresActive On
    ExpiresByType text/css A31536000
    ExpiresByType text/x-component A31536000
    ExpiresByType application/x-javascript A31536000
    ExpiresByType application/javascript A31536000
    ExpiresByType text/javascript A31536000
    ExpiresByType text/x-js A31536000
    ExpiresByType text/html A3600
    ExpiresByType text/richtext A3600
    ExpiresByType text/plain A3600
    ExpiresByType text/xsd A3600
    ExpiresByType text/xsl A3600
    ExpiresByType text/xml A3600
    ExpiresByType video/asf A31536000
    ExpiresByType video/avi A31536000
    ExpiresByType image/bmp A31536000
    ExpiresByType application/java A31536000
    ExpiresByType video/divx A31536000
    ExpiresByType application/msword A31536000
    ExpiresByType image/gif A31536000
    ExpiresByType application/x-gzip A31536000
    ExpiresByType image/x-icon A31536000
    ExpiresByType image/jpeg A31536000
    ExpiresByType image/webp A31536000
    ExpiresByType application/json A31536000
    ExpiresByType audio/midi A31536000
    ExpiresByType video/quicktime A31536000
    ExpiresByType audio/mpeg A31536000
    ExpiresByType video/mp4 A31536000
    ExpiresByType video/mpeg A31536000
    ExpiresByType video/webm A31536000
    ExpiresByType application/x-font-otf A31536000
    ExpiresByType audio/ogg A31536000
    ExpiresByType application/pdf A31536000
    ExpiresByType image/png A31536000
    ExpiresByType audio/x-realaudio A31536000
    ExpiresByType image/svg+xml A31536000
    ExpiresByType application/x-shockwave-flash A31536000
    ExpiresByType application/x-tar A31536000
    ExpiresByType image/tiff A31536000
    ExpiresByType application/x-font-ttf A31536000
    ExpiresByType audio/wav A31536000
    ExpiresByType audio/wma A31536000
    ExpiresByType application/font-woff A31536000
    ExpiresByType application/font-woff2 A31536000
    ExpiresByType application/zip A31536000
</IfModule>
# END CACHE POLICY

Set­up the new Word­Press in­stance via the Web in­ter­face

WP-CLI Set­up

WP-CLI is the com­mand-line in­ter­face for Word­Press. You can up­date plu­g­ins, con­fig­ure mul­ti­site in­stal­la­tions and much more, with­out us­ing a web brows­er.

In­stal­la­tion

cd /tmp && \
curl -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar
php wp-cli.phar --info
chmod +x wp-cli.phar
sudo mv wp-cli.phar /usr/local/bin/wp

Up­date

sudo wp cli update

Tab com­ple­tions

# https://raw.githubusercontent.com/wp-cli/wp-cli/v2.5.0/utils/wp-completion.bash
cd /tmp && \
curl -O https://raw.githubusercontent.com/wp-cli/wp-cli/master/utils/wp-completion.bash && \
sudo mv wp-completion.bash /usr/local/bin/
cat << EOF | tee -a ~/.profile

# wp-cli tab completions, https://wp-cli.org/
. /usr/local/bin/wp-completion.bash
EOF

Ref­er­ences