Wordpress on Azure PaaS
Update 14th Oct 2020
I’ve written about using cloud-init on a raw Azure VM which I really like and would urge you to consider instead.
Introduction
Install Wordpress on Azure Windows PaaS (Platform as a Service):
I have written a lot on different Wordpress hosting options in Azure, backed up with years of trying different options and the evolution of Azure.
Here I’m going to cover:
- Azure App Server
- MySQL setup
- Wordpress
- Deploying via local git
- wp-config.php
- .user.ini
- All in One WP Migration
- WP Super Cache
- SSL
Then finally I’m going to say if you’re using a power hungry plugin such as the Divi Plugin I wouldn’t recommend Azure PaaS.
Azure App Service
Create an App Service Plan in it’s own resource group eg DaveWebsites
Create an App Service eg davemateercom using the default Application Insights settings
Change app settings PHP7.2
Check always on is on.
MySQL
Create a hosted MySQL database in own resource group eg DaveWebsitesMySQL. Be wary of the Basic pricing tier as the performance can be very variable.
db server: dave.mysql.database.azure.com
dbname: davemateercom
user: davedbuser@dave
password: asdflkj23989$$$
Allow access for Azure services, and Disable Enforce SSL
Cloud shell for:
mysql --host dave.mysql.database.azure.com --user davedbuser@dave -p
# create a databasename the same as the website app service
create database davemateercom;
Export and Import
I’ve had to export the database and import into a higher pricing tier. Here is how to do it:
# export
mysqldump --host dave.mysql.database.azure.com --user davedbuser@dave -p davemateercom > dump.sql
# import
mysql --host davenew.mysql.database.azure.com --user davedbusernew@davenew -p davemateercomnew < dump.sql
and then in wp-config.php
in the site root:
define( 'DB_NAME', 'davemateercomnew' );
/** MySQL database username */
define( 'DB_USER', 'davedbusernew@davenew' );
/** MySQL database password */
define( 'DB_PASSWORD', 'asdflkj23989$$$' );
/** MySQL hostname */
define( 'DB_HOST', 'davenew.mysql.database.azure.com' );
Wordpress
Download Wordpress and unzip in a local folder
Setup a remote repo eg GitHub, BitBucket or Azure Devops. This is to store the files, and is not part of the deployment pipeline.
cd c:\dev\davemateercom
#copy all wordpress files in here
git init
git add .
git commit -m "init"
git remote add origin https://xxxxx.visualstudio.com/davemateercomxxxxx/_git/davemateercomxxxxx
git push -u origin --all
Deployment Center
Setup app service deployment center in Azure
Then select the App Service Kudu Build server
Then link the local git repo to the new remote. To be clear we have 2 remotes now. One is the live website, the other is GitHub or BitBucket..
git remote add qnrlcom https://$qnrlcom@qnrlcom.scm.azurewebsites.net:443/qnrlcom.git
Notice the $qnrlcom username
If you type in the wrong password see here to clear the local windows cache
wp-config.php
-Edit wp-config.php locally -if this is first time rename wp-config-sample.php to wp-config.php -edit MySQL settings putting in dbname, username etc.. -edit Unique keys and salts using this url
if updating this file and using WP Super Cache, put these lines in too
// the WP Super Cache plugin usually puts these in for us, but easier to keep in source control
define( 'WPCACHEHOME', 'D:\home\site\wwwroot\wp-content\plugins\wp-super-cache/' );
define('WP_CACHE', true);
Some documentation suggest putting int @ini_set in wp_config. I found using .user.ini to work as show in the next section.
// value of post_max_size must be large that upload
// didn't seem to work from here
/*@ini_set( 'upload_max_filesize' , '512M' );
@ini_set( 'post_max_size', '512M');
@ini_set( 'memory_limit', '256M' );
@ini_set( 'max_execution_time', '300' );
@ini_set( 'max_input_time', '300' );
*/
.user.ini
Rather than generating the file on the server I keep it in source control!
App Service, Advanced Tools, Kudu, Debug console, CMD
# /site/wwwroot/
# touch .user.ini (had to do it twice before it worked)
upload_max_filesize = 256M
# post size must be bigger than upload
post_max_size = 512M
# was 256 by trying more for divi theme
memory_limit = 512M
max_execution_time = 300
max_input_time = 300
# for divi theme
max_input_vars = 3000
; Example Settings
;display_errors=On
; OPTIONAL: Turn this on to write errors to d:\home\LogFiles\php_errors.log
log_errors=On
Restarting the app is important to get it to read as 5 minutes cache.
PHP Errors
Turn on php log_errors shown above
/LogFiles
php errors
Diagnostic Logs and Log Stream
Turn on Diagnostics Logs
Go to log stream and search for any obvious non 200 errors
web.config
Again I put this file into source control.
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<system.webServer>
<staticContent>
<!-- stop woff 404.3 errors on admin dashboard -->
<mimeMap fileExtension=".woff" mimeType="application/x-font-woff" />
</staticContent>
<security>
<requestFiltering>
<!-- Allow upload of large files for restoring -->
<!-- This will handle requests up to 700MB (CD700) -->
<requestLimits maxAllowedContentLength="737280000" />
</requestFiltering>
</security>
<rewrite>
<rules>
<rule name="WordPress: https://davexxxxxxx.azurewebsites.net" patternSyntax="Wildcard">
<match url="*"/>
<conditions>
<add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true"/>
<add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true"/>
</conditions>
<action type="Rewrite" url="index.php"/>
</rule>
</rules>
</rewrite>
</system.webServer>
</configuration>
woff is to stop 404.3 errors which happened on admin dashboard for missing mimetypes - fonts.
maxAllowedContentLength is so I can upload large (300MB) files to the All in One WP Migration plugin. This is to do a restore of a site.
The other rewrite section was written in by default by Azure. go to the website and put in some test values for username and password (as I’m usually restoring a backup)
WP_DEBUG
To turn on Wordpress debug and writing to log file, which gets written in wp-content\debug.log
define( 'WP_DEBUG', true );
define( 'WP_DEBUG_LOG', true );
Divi Theme
Divi theme has a support center page which displays useful PHP variables such as: PHP Version, Post Max size etc..
All in One WP Migration
This is a very useful tool to transfer entire websites (eg all files and db)
To set the upload_max_filesize in Azure it seems you need the .ini file there too. All in One Docs
WP Super Cache
WP Super Cache will write this into the wp-config. Be careful when testing that you are not logged in, as by default this wont hit the cache.
// the WP Super Cache plugin usually puts these in for us, but easier to keep in source control
define( 'WPCACHEHOME', 'D:\home\site\wwwroot\wp-content\plugins\wp-super-cache/' );
define('WP_CACHE', true);
then it will write files into /wwwroot/wp-content/cache/supercache/nameofsite.com This plugin is in charge of the /cache directory
I use 86400ms which is 1 day for the cache timeout. Am trying 0 which means that it should remain there forever and not be cleaned up.
Chrome developer tools (F12) and initial page response is 2.8seconds. I am on a slow database which is the probable cause of that.
Go to a page and look at the source. You should see this:
<!-- Dynamic page generated in 2.305 seconds. -->
<!-- Cached page generated by WP-Super-Cache on 2019-02-27 16:15:52 -->
<!-- super cache -->
Ctrl F5 the page and we are now at 221ms (notice all the assets are being reloaded)
Notice a problem here and not rendering. We are using Visual Builder. The trick is to save out a page, which runs the css minifier.
In Summary for a 8.5MB homepage:
- no server file caching (5.8s)
- ctrl f5 no browser caching, and server caching on (1.4-4s)
- f5 browser caching of assets (760-900ms)
We have not enabled sending 304 not modified (thus never getting a browser cached page), nor some of the other recommended features yet.
IIS Caching
It seems that we go down from 250ms to 60ms after some reloads, so IIS is probably doing Dynamic Output Caching
Seeding the cache
It can take some some for the cache to be properly built. It is useful to know the final number of pages:
I have my own seeder, but it seems it doesn’t always get all the pages, or the pages take a while to build
after 2 runs I was back up to all pages cached.
For debugging issues the log is excellent:
Debugging
I have totally broken the site by clicking on the enable debug option in WP Super Cache.
Turned out the _ should be a $.
wp-config.php
I found this by changing wp-config.php. There are 2 useful debug settings:
define( 'WP_DEBUG', true );
define( 'WP_DEBUG_LOG', true );
.user.ini
upload_max_filesize = 512M
post_max_size = 512M
## trying 512 for memory as issues with perf and memory usage on backend
memory_limit = 256M
max_execution_time = 300
max_input_time = 300
; Example Settings
;display_errors=On
; OPTIONAL: Turn this on to write errors to d:\home\LogFiles\php_errors.log
log_errors=On
Because it was a php parse error it didn’t show in my logs. maybe this would help
web.config
I tried to see if IIS logs would help
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<system.webServer>
<!-- added to diagnose 500 errors -->
<httpErrors errorMode="Detailed" />
<!-- stop woff 404.3 errors on admin dashboard -->
<staticContent>
<mimeMap fileExtension=".woff" mimeType="application/x-font-woff" />
</staticContent>
<!-- Allow upload of large files for restoring -->
<security>
<requestFiltering>
<!-- This will handle requests up to 700MB (CD700) -->
<requestLimits maxAllowedContentLength="737280000" />
</requestFiltering>
</security>
<rewrite>
<rules>
<rule name="WordPress: https://qnrlcom.azurewebsites.net" patternSyntax="Wildcard">
<match url="*"/>
<conditions>
<add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true"/>
<add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true"/>
</conditions>
<action type="Rewrite" url="index.php"/>
</rule></rules>
</rewrite>
</system.webServer>
<!-- added to diagnose 500 errors -->
<system.web>
<customErrors mode="Off"/>
<compilation debug="true"/>
</system.web>
</configuration>
but it was further down the pipeline so this didn’t help - 2 places I added diagnose 500 errors code.
SSL setup
As we are using Azure PaaS on Windows we can use an automated LetsEncrypt cert extension, however for mission critical apps I’m choosing not to at the moment.
Performance
What we’ve found is the the Divi Plugin we’ve been using is very power hungry, so the backend administration of the site is slow. Front end is excellent with the caching plugin.
In the future I may seriously look at a more custom WP hosting provider eg Kinsta