Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save sys0dm1n/b07b8a783fd1931a4118aeca42e13e2e to your computer and use it in GitHub Desktop.
Save sys0dm1n/b07b8a783fd1931a4118aeca42e13e2e to your computer and use it in GitHub Desktop.
HAproxy, Varnish and Apahce configuration files compatible for Wordpress with Apache webserver with Let's Encrypt
Configuration files to setup Wordpress with Apache2 webserver, Varnish caching server and HAproxy for loadbalancing with support for Lets Encrypt
#Apache default vhost. This is required for HAproxy IP check
<VirtualHost *:80>
ServerAdmin support@website.com
DocumentRoot /var/web/empty
<Directory /var/web/empty>
Options FollowSymLinks
AllowOverride None
</Directory>
ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined
</VirtualHost>
#HAproxy version: 1.6.13-1ppa1~trusty
# https://cbonte.github.io/haproxy-dconv/configuration-1.5.html#send-proxy
# https://blog.feryn.eu/varnish-4-1-haproxy-get-the-real-ip-by-leveraging-proxy-protocol-support/
global
# log /dev/log local0
# log /dev/log local1 notice
chroot /var/lib/haproxy
log 127.0.0.1 local0
log 127.0.0.1 local1 notice
# log /var/log/haproxy.log local0
# log /var/log/haproxy.log local1 notice
user haproxy
group haproxy
daemon
tune.ssl.default-dh-param 2048
maxconn 2048
defaults
log global
mode http
option httplog
option dontlognull
option forwardfor header X-Forwarded-For
option http-server-close
timeout connect 5s
timeout client 50s
timeout server 50s
errorfile 400 /etc/haproxy/errors/400.http
errorfile 403 /etc/haproxy/errors/403.http
errorfile 408 /etc/haproxy/errors/408.http
errorfile 500 /etc/haproxy/errors/500.http
errorfile 502 /etc/haproxy/errors/502.http
errorfile 503 /etc/haproxy/errors/503.http
errorfile 504 /etc/haproxy/errors/504.http
## If switching website from http to https, uncomment below to keep the facebook likes
#frontend www
# bind *:80
# mode http
# reqadd X-Forwarded-Proto:\ http
# acl facebook_user_agent hdr_sub(User-Agent) -i Facebot facebookexternalhit/1.1
# use_backend www-backend-fb if facebook_user_agent
# default_backend varnish_pool
frontend www-https
bind *:443 ssl crt /etc/haproxy/certs/<website.com>.pem
reqadd X-Forwarded-Proto:\ https
reqadd HTTP_X_FORWARDED_PROTO:\ https
reqadd HTTP-X-FORWARDED-PROTO:\ https
reqadd X-Forwarded-Port:\ 443
acl letsencrypt-acl path_beg /.well-known/acme-challenge/
use_backend letsencrypt-backend if letsencrypt-acl
## If switching website from http to https, uncomment below to keep the facebook likes
# acl facebook_user_agent hdr_sub(User-Agent) -i Facebot facebookexternalhit/1.1
# use_backend www-backend-fb if facebook_user_agent
default_backend varnish_pool
## If switching website from http to https, uncomment below to keep the facebook likes
#backend www-backend-fb
# balance roundrobin
# mode http
# reqadd X-Forwarded-Proto:\ https
# reqadd HTTP_X_FORWARDED_PROTO:\ https
# reqadd HTTP-X-FORWARDED-PROTO:\ https
# default-server inter 15s fall 3 rise 2
# option httpchk GET / HTTP/1.0
# http-check expect status 200
# server front1 <front1-IP>:80 check
# server front2 <front2-IP>:80 check
# server front3 <front3-IP>:80 check
backend varnish_pool
redirect scheme https if !{ ssl_fc }
# Use the roundrobin strategy for distributing load amongst the servers
# leastconn: The server with the lowest number of connections receives the connection. This is better for servers with long-running connections (LDAP, SQL, TSE), but not necessarily for short-lived connections (HTTP).
balance roundrobin
# pass HTTP requests to the servers listed
mode http
http-response set-header X-Frame-Options DENY
http-response set-header X-XSS-Protection 1;mode=block
http-response set-header X-Content-Type-Options nosniff
http-response set-header Referrer-Policy no-referrer-when-downgrade
default-server inter 15s fall 3 rise 2
option httpchk GET / HTTP/1.0
http-check expect status 200
server front1 <front1-IP>:80 check
server front2 <front2-IP>:80 check
server front3 <front3-IP>:80 check
backend letsencrypt-backend
server letsencrypt 127.0.0.1:54321
listen statistics
bind *:1936
mode http
stats enable
stats refresh 30s
stats show-node
stats uri /stats
stats hide-version
stats auth <username>:<password>
<VirtualHost *:80>
ServerAdmin support@website.com
DocumentRoot /var/sites/mywebsite
ServerName website.com
ServerAlias www.website.com cdn.website.com
# Redirect www to non-wwww URL
<If "%{HTTP_HOST} = 'www.website.com'">
Redirect 301 "/" "http://website.com/"
</If>
# For CDN separate vhost, used to allow access only to the website through the main domain name website.com
# <IfModule mod_rewrite.c>
# RewriteEngine on
# RewriteCond %{REQUEST_FILENAME} !\.(ico|pdf|jar|flv|jpg|jpeg|pict|eot|ejs|eps|otf|pls|png|bmp|class|gif|svg|svgz|js|css|swf|webp|html|htm|woff|woff2|ttf|tif|tiff|rtf|doc|docx|pdf|ppt|pptx|xls|xlsx|csv|xml|txt|midi|mid|bz2|bzip|gzip|zip|rar|gz|tar.gz)(\.gz)?(\?.*)?$ [NC]
# RewriteCond %{HTTP_HOST} ^cdn\.website\.com$ [NC]
# RewriteRule ^(.*)$ https://website.com/$1 [L,R=301]
# </IfModule>
<Directory /var/sites/mywebsite/>
Options -Indexes +FollowSymLinks -MultiViews
AllowOverride None
Require all granted
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]
##Wordpress multi site Apache configuration
# add a trailing slash to /wp-admin
# RewriteRule ^([_0-9a-zA-Z-]+/)?wp-admin$ $1wp-admin/ [R=301,L]
# RewriteCond %{REQUEST_FILENAME} -f [OR]
# RewriteCond %{REQUEST_FILENAME} -d
# RewriteRule ^ - [L]
# RewriteRule ^([_0-9a-zA-Z-]+/)(wp-(content|admin|includes).*) $2 [L]
# RewriteRule ^([_0-9a-zA-Z-]+/)(.*\.php)$ $2 [L]
# RewriteRule . index.php [L]
## Securing wp-includes
RewriteBase /
RewriteRule ^wp-admin/includes/ - [F,L]
RewriteRule !^wp-includes/ - [S=3]
RewriteRule ^wp-includes/[^/]+\.php$ - [F,L]
RewriteRule ^wp-includes/js/tinymce/langs/.+\.php - [F,L]
RewriteRule ^wp-includes/theme-compat/ - [F,L]
## disallow acces to admin backoffice
# RewriteCond %{REQUEST_URI} ^(.*)?wp-login\.php(.*)$ [OR]
# RewriteCond %{REQUEST_URI} ^(.*)?wp-admin$
# RewriteRule ^/?(.*) http:\/\/admin.website.com/$1 [R=301,L]
# RedirectMatch /wp-login* /404.php
# RedirectMatch /wp-admin* /404.php
# If using Vagrant, disallow access
# RewriteRule ^vagrantfile - [F,NC,L]
</IfModule>
</Directory>
## Securing wp-config.php
<files wp-config.php>
Require all denied
</files>
# BEGIN Browser Cache
<IfModule mod_mime.c>
AddType text/css .css
AddType application/x-javascript .js
AddType text/x-component .htc
AddType text/html .html .htm
AddType text/richtext .rtf .rtx
AddType image/svg+xml .svg .svgz
AddType text/plain .txt
AddType text/xsd .xsd
AddType text/xsl .xsl
AddType text/xml .xml
AddType video/asf .asf .asx .wax .wmv .wmx
AddType video/avi .avi
AddType image/bmp .bmp
AddType application/java .class
AddType video/divx .divx
AddType application/msword .doc .docx
AddType application/vnd.ms-fontobject .eot
AddType application/x-msdownload .exe
AddType image/gif .gif
AddType application/x-gzip .gz .gzip
AddType image/x-icon .ico
AddType image/jpeg .jpg .jpeg .jpe
AddType application/vnd.ms-access .mdb
AddType audio/midi .mid .midi
AddType video/quicktime .mov .qt
AddType audio/mpeg .mp3 .m4a
AddType video/mp4 .mp4 .m4v
AddType video/mpeg .mpeg .mpg .mpe
AddType application/vnd.ms-project .mpp
AddType application/x-font-otf .otf
AddType application/vnd.oasis.opendocument.database .odb
AddType application/vnd.oasis.opendocument.chart .odc
AddType application/vnd.oasis.opendocument.formula .odf
AddType application/vnd.oasis.opendocument.graphics .odg
AddType application/vnd.oasis.opendocument.presentation .odp
AddType application/vnd.oasis.opendocument.spreadsheet .ods
AddType application/vnd.oasis.opendocument.text .odt
AddType audio/ogg .ogg
AddType application/pdf .pdf
AddType image/png .png
AddType application/vnd.ms-powerpoint .pot .pps .ppt .pptx
AddType audio/x-realaudio .ra .ram
AddType application/x-shockwave-flash .swf
AddType application/x-tar .tar
AddType image/tiff .tif .tiff
AddType application/x-font-ttf .ttf .ttc
AddType audio/wav .wav
AddType audio/wma .wma
AddType application/vnd.ms-write .wri
AddType application/vnd.ms-excel .xla .xls .xlsx .xlt .xlw
AddType application/zip .zip
</IfModule>
<IfModule mod_expires.c>
ExpiresActive On
ExpiresDefault "access plus 60 days"
ExpiresByType text/css A31536000
ExpiresByType application/x-javascript A31536000
ExpiresByType text/x-component A31536000
ExpiresByType text/html A3600
ExpiresByType text/richtext A3600
ExpiresByType image/svg+xml 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 application/vnd.ms-fontobject A31536000
ExpiresByType application/x-msdownload A31536000
ExpiresByType image/gif A31536000
ExpiresByType application/x-gzip A31536000
ExpiresByType image/x-icon A31536000
ExpiresByType image/jpeg A31536000
ExpiresByType application/vnd.ms-access A31536000
ExpiresByType audio/midi A31536000
ExpiresByType video/quicktime A31536000
ExpiresByType audio/mpeg A31536000
ExpiresByType video/mp4 A31536000
ExpiresByType video/mpeg A31536000
ExpiresByType application/vnd.ms-project A31536000
ExpiresByType application/x-font-otf A31536000
ExpiresByType application/vnd.oasis.opendocument.database A31536000
ExpiresByType application/vnd.oasis.opendocument.chart A31536000
ExpiresByType application/vnd.oasis.opendocument.formula A31536000
ExpiresByType application/vnd.oasis.opendocument.graphics A31536000
ExpiresByType application/vnd.oasis.opendocument.presentation A31536000
ExpiresByType application/vnd.oasis.opendocument.spreadsheet A31536000
ExpiresByType application/vnd.oasis.opendocument.text A31536000
ExpiresByType audio/ogg A31536000
ExpiresByType application/pdf A31536000
ExpiresByType image/png A31536000
ExpiresByType application/vnd.ms-powerpoint 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/vnd.ms-write A31536000
ExpiresByType application/vnd.ms-excel A31536000
ExpiresByType application/zip A31536000
</IfModule>
<IfModule mod_deflate.c>
<IfModule mod_setenvif.c>
BrowserMatch ^Mozilla/4 gzip-only-text/html
BrowserMatch ^Mozilla/4\.0[678] no-gzip
BrowserMatch \bMSIE !no-gzip !gzip-only-text/html
BrowserMatch \bMSI[E] !no-gzip !gzip-only-text/html
</IfModule>
<IfModule mod_headers.c>
Header append Vary User-Agent env=!dont-vary
</IfModule>
<IfModule mod_filter.c>
AddOutputFilterByType DEFLATE text/css application/x-javascript text/x-component text/html text/richtext image/svg+xml text/plain text/xsd text/xsl text/xml image/x-icon
</IfModule>
</IfModule>
<FilesMatch "\.(css|js|htc|CSS|JS|HTC)$">
<IfModule mod_headers.c>
Header set Pragma "public"
Header append Cache-Control "public, must-revalidate, proxy-revalidate"
</IfModule>
FileETag MTime Size
<IfModule mod_headers.c>
Header set X-Powered-By "CompanyName/0.1"
</IfModule>
</FilesMatch>
<FilesMatch "\.(html|htm|rtf|rtx|svg|svgz|txt|xsd|xsl|xml|HTML|HTM|RTF|RTX|SVG|SVGZ|TXT|XSD|XSL|XML)$">
<IfModule mod_headers.c>
Header set Pragma "public"
Header append Cache-Control "public, must-revalidate, proxy-revalidate"
</IfModule>
FileETag MTime Size
<IfModule mod_headers.c>
Header set X-Powered-By "CompanyName/0.1"
</IfModule>
</FilesMatch>
<FilesMatch "\.(asf|asx|wax|wmv|wmx|avi|bmp|class|divx|doc|docx|eot|exe|gif|gz|gzip|ico|jpg|jpeg|jpe|mdb|mid|midi|mov|qt|mp3|m4a|mp4|m4v|mpeg|mpg|mpe|mpp|otf|odb|odc|odf|odg|odp|ods|odt|ogg|pdf|png|pot|pps|ppt|pptx|ra|ram|svg|svgz|swf|tar|tif|tiff|ttf|ttc|wav|wma|wri|xla|xls|xlsx|xlt|xlw|zip|ASF|ASX|WAX|WMV|WMX|AVI|BMP|CLASS|DIVX|DOC|DOCX|EOT|EXE|GIF|GZ|GZIP|ICO|JPG|JPEG|JPE|MDB|MID|MIDI|MOV|QT|MP3|M4A|MP4|M4V|MPEG|MPG|MPE|MPP|OTF|ODB|ODC|ODF|ODG|ODP|ODS|ODT|OGG|PDF|PNG|POT|PPS|PPT|PPTX|RA|RAM|SVG|SVGZ|SWF|TAR|TIF|TIFF|TTF|TTC|WAV|WMA|WRI|XLA|XLS|XLSX|XLT|XLW|ZIP)$">
<IfModule mod_headers.c>
Header set Pragma "public"
Header append Cache-Control "public, must-revalidate, proxy-revalidate"
</IfModule>
FileETag MTime Size
<IfModule mod_headers.c>
Header set X-Powered-By "CompanyName/0.1"
</IfModule>
</FilesMatch>
ErrorLog ${APACHE_LOG_DIR}/error-website.log
CustomLog ${APACHE_LOG_DIR}/access-website.log combined
</VirtualHost>
## Varnish version: 3.0.7-1~trusty
acl Blacklist {
"172.16.54.2";
"10.0.0.25";
"192.168.0.1";
}
backend default {
.host = "<webserver-IP";
.port = "80";
.connect_timeout = 120s;
.first_byte_timeout = 120s;
.between_bytes_timeout = 120s;
}
acl purge {
"localhost";
"127.0.0.1";
"<IP1>";
"<Network-IP1>"/28;
"<Network-IP2>"/28;
}
acl passem {
# "194.xxx.xxx.xxx"; ## Office Public IP
}
## VARNISH PURGE PLUGIN -- begin -- ##
# Regex purging
# Treat the request URL as a regular expression.
sub purge_regex {
ban("obj.http.X-Req-URL ~ " + req.url + " && obj.http.X-Req-Host == " + req.http.host);
}
# Page purging (default)
# Use the exact request URL, but ignore any query params
sub purge_page {
set req.url = regsub(req.url, "\?.*$", "");
ban("obj.http.X-Req-URL-Base == " + req.url + " && obj.http.X-Req-Host == " + req.http.host);
}
## VARNISH PURGE PLUGIN -- end -- ##
## VARNISH MOBILE DETECTION -- begin -- ##
sub detect_device {
# Define the desktop device
set req.http.X-Device = "desktop";
if (req.http.User-Agent ~ "iP(hone|od)" || req.http.User-Agent ~ "Android" || req.http.User-Agent ~ "iPad") {
# Define smartphones and tablets
set req.http.X-Device = "smart";
}
elseif (req.http.User-Agent ~ "SymbianOS" || req.http.User-Agent ~ "^BlackBerry" || req.http.User-Agent ~ "^SonyEricsson" || req.http.User-Agent ~ "^Nokia" ||
req.http.User-Agent ~ "^SAMSUNG" || req.http.User-Agent ~ "^LG") {
# Define every other mobile device
set req.http.X-Device = "other";
}
}
## VARNISH MOBILE DETECTION -- end -- ##
sub vcl_recv {
call detect_device;
# Shortcut for DFind requests
if (req.url ~ "^/w00tw00t") {
error 404 "Not Found";
}
if ( (client.ip ~ Blacklist) ) {
error 403 "Access denied";
}
if(req.url ~ "/xmlrpc.php"){
error 403 "Access denied";
}
if(req.url ~ "127.0.0.1"){
error 404 "Access denied";
}
# Compatibility with Apache format log
if (req.restarts == 0) {
if (req.http.x-forwarded-for) {
set req.http.X-Forwarded-For = req.http.X-Forwarded-For + ", " + client.ip;
} else {
set req.http.X-Forwarded-For = client.ip;
}
}
if (req.request != "GET" &&
req.request != "HEAD" &&
req.request != "PUT" &&
req.request != "POST" &&
req.request != "TRACE" &&
req.request != "OPTIONS" &&
req.request != "DELETE" &&
req.request != "PURGE") {
return (pipe);
}
# normalize Aceept-Encoding header
# http://varnish.projects.linpro.no/wiki/FAQ/Compression
if (req.http.Accept-Encoding) {
if (req.url ~ "\.(jpg|png|gif|gz|tgz|bz2|tbz|mp3|ogg|swf|flv)$") {
# No point in compressing these
remove req.http.Accept-Encoding;
} elsif (req.http.Accept-Encoding ~ "gzip") {
set req.http.Accept-Encoding = "gzip";
} elsif (req.http.Accept-Encoding ~ "deflate" && req.http.user-agent !~ "MSIE") {
set req.http.Accept-Encoding = "deflate";
} else {
# unkown algorithm
remove req.http.Accept-Encoding;
}
}
## VARNISH PURGE PLUGIN -- begin -- ##
if (req.request == "PURGE" || req.request == "BAN") {
if (client.ip !~ purge) {
error 405 "Not allowed.";
}
if (req.http.X-Purge-Method) {
if (req.http.X-Purge-Method ~ "(?i)regex") {
call purge_regex;
} else {
call purge_page;
}
} else {
# No X-Purge-Method header was specified.
# Do our best to figure out which one they want.
if (req.url ~ "\.\*" || req.url ~ "^\^" || req.url ~ "\$$" || req.url ~ "\\[.?*+^$|()]") {
call purge_regex;
} else {
call purge_page;
}
}
error 200 "Purged.";
}
## VARNISH PURGE PLUGIN -- end -- ##
### RETURN PASS -- begin -- ###
# if (req.http.Authorization || req.http.Cookie) {
# Not cacheable by default
# return (pass);
# }
# Exit cache if remote IP matches
if (client.ip ~ passem) {
return (pass);
}
# Don't cache ajax requests
if(req.http.X-Requested-With == "XMLHttpRequest" || req.url ~ "nocache" || req.request == "POST" || req.url ~ "(routes.php|control.php|wp-comments-post.php|wp-login.php|bb-login.php|bb-reset-password.php|register.php)") {
return (pass);
}
if (req.url ~ "/wp-(login|admin)") {
return (pass);
}
# Dont cache the RSS feed
if (req.url ~ "feed") {
return (pass);
}
# Don't cache sitemap.xml
if(req.url ~ "/sitemap.xml"){
return (pass);
}
if (req.url ~ "/wp-(login|admin)") {
return (pass);
}
### RETURN PASS -- end -- ###
# shortcut for DFind requests
if (req.url ~ "^/w00tw00t") {
error 404 "Not Found";
}
if (req.http.cookie ~ "(wordpress_|wp-settings-|no_cache)") {
return(pass);
} else {
unset req.http.cookie;
return(lookup);
}
}
sub vcl_hash {
#Existing hash configuration
# And then add the device to the hash (if its a mobile device)
if (req.http.X-Device ~ "smart" || req.http.X-Device ~ "other") {
# set req.hash += req.http.X-Device;
hash_data(req.http.X-Device);
}
}
sub vcl_fetch {
# Pass basic auth for Apache
#if (req.http.Authorization || req.http.Authenticate) {
# return (hit_for_pass);
#}
# VARNISH PURGE
set beresp.http.X-Req-Host = req.http.host;
set beresp.http.X-Req-URL = req.url;
set beresp.http.X-Req-URL-Base = regsub(req.url, "\?.*$", "");
# VARNISH PURGE
# Remove User-Agent before caching
if (beresp.http.Vary ~ "User-Agent") {
unset beresp.http.Vary;
}
if (req.url ~ "(wp-(login|admin)|logout)" || req.url ~ "preview=true" || req.url ~ "xmlrpc.php" || req.request == "POST") {
return (hit_for_pass);
}
if (req.request == "GET" ) {
unset beresp.http.set-cookie;
}
if (req.url ~ "\.(gif|jpg|jpeg|swf|css|js|flv|mp3|mp4|pdf|ico|png)(\?.*|)$") {
set beresp.ttl = 4h;
}
if (beresp.http.Content-Type ~ "text/html" || beresp.http.Content-Type ~ "text/xml" || beresp.http.Content-Type ~ "text/htm"){
set beresp.ttl = 15m;
}
set beresp.grace = 6h;
}
sub vcl_deliver {
# multi-server webfarm? set a variable here so you can check
# the headers to see which frontend served the request
# set resp.http.X-Server = "server-01";
if (obj.hits > 0) {
set resp.http.X-Cache = "HIT";
set resp.http.X-Cache-Hits = obj.hits;
} else {
set resp.http.X-Cache = "MISS";
}
# Remove some headers: PHP version
unset resp.http.X-Powered-By;
# Remove some headers: Apache version & OS
unset resp.http.Server;
# Remove some heanders: Varnish
unset resp.http.Via;
unset resp.http.X-Varnish;
# VARNISH PURGE
unset resp.http.X-Req-Host;
unset resp.http.X-Req-URL;
unset resp.http.X-Req-URL-Base;
# VARNISH PURGE
return (deliver);
}
sub vcl_error {
set obj.http.Content-Type = "text/html; charset=utf-8";
if (obj.status == 404) {
synthetic {"
<!DOCTYPE html>
<html>
<head>
<title>404 Access denied!</title>
</head>
<body>
<h1>Error 404 Access denied!</h1>
<p>Access Denied</p>
</body>
</html>
"};
} else {
synthetic {"
<!DOCTYPE html>
<html>
<head>
<title>An Error has accurred</title>
</head>
<body>
<h1>An Error has accurred</h1>
<p></p>
</body>
</html>
"};
}
return (deliver);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment