Apache .htaccess disable access to site during Maintenance Mode / Deployment

Because I have to look this up from time to time, a note to myself: Add the contents below to your .htaccess to have Apache respond with a “Temporarily Unavailable” message in case a .maintenance file exists. — Handy during deploys

RewriteEngine On

# Show "Temporarily Unavailable" page if there's a .maintenance file present
RewriteCond %{DOCUMENT_ROOT}/.maintenance -f
RewriteRule .* - [R=503,L]

~

Did this help you out? Like what you see?
Thank me with a coffee.

I don't do this for profit but a small one-time donation would surely put a smile on my face. Thanks!

☕️ Buy me a Coffee (€3)

To stay in the loop you can follow @bramus or follow @bramusblog on Twitter.

Apache .htaccess trim www. prefix from domain name

Because I have to look this up from time to time, a note to myself: Add the contents below to your .htaccess to have Apache trim the www. prefix from URLs while preserving the rest of the URL (domain name, querystring, etc).

RewriteEngine On

# Trim www prefix
RewriteCond %{HTTP_HOST} ^www\.(.+)$ [NC]
RewriteRule ^ https://%1%{REQUEST_URI} [R=301,L]

~

Did this help you out? Like what you see?
Thank me with a coffee.

I don't do this for profit but a small one-time donation would surely put a smile on my face. Thanks!

☕️ Buy me a Coffee (€3)

To stay in the loop you can follow @bramus or follow @bramusblog on Twitter.

Apache .htaccess enforce HTTPS

Because I have to look this up from time to time, a note to myself: Add the contents below to your .htaccess to have Apache enforce HTTPS while preserving the rest of the URL (domain name, querystring, etc).

RewriteEngine On

# Enforce HTTPS (everywhere)
RewriteCond %{HTTPS} off
RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]

~

Did this help you out? Like what you see?
Thank me with a coffee.

I don't do this for profit but a small one-time donation would surely put a smile on my face. Thanks!

☕️ Buy me a Coffee (€3)

To stay in the loop you can follow @bramus or follow @bramusblog on Twitter.

Fixing ab (ApacheBench) Error "apr_socket_connect(): Invalid argument (22)"

Note to self: when running ApacheBench it does not know how to resolve localhost. Instead you’ll have to use 127.0.0.1. I seem to forget this every single time I use it 🤦‍♂️

~

Won’t work: localhost

$ ab -n 5000 -c 50 http://localhost:8080/

This is ApacheBench, Version 2.3 <$Revision: 1843412 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking localhost (be patient)

Test aborted after 10 failures

apr_socket_connect(): Invalid argument (22)

Will work: 127.0.0.1

$ ab -n 5000 -c 50 http://127.0.0.1:8080/
This is ApacheBench, Version 2.3 <$Revision: 1843412 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking 127.0.0.1 (be patient)
Completed 1000 requests
Completed 2000 requests
Completed 3000 requests
Completed 4000 requests
Completed 5000 requests
Finished 5000 requests

…

~

Did this help you out? Like what you see?
Thank me with a coffee.

I don't do this for profit but a small one-time donation would surely put a smile on my face. Thanks!

☕️ Buy me a Coffee (€4)

To stay in the loop you can follow @bramus or follow @bramusblog on Twitter.

Run a PHP app in a Docker Container (Notes)

The past week I took a closer look at the several options on how to run a PHP app with Docker. In the past I’ve ran a few pre-built containers, but now I wanted to truly get to the bottom of it all as I don’t always need a full blown container with all extensions, but want to start from a rather clean base and build upon that. This post mainly functions as a note to my future self and lists projects that I found interesting during my research.

ℹ️ I was only looking to run a PHP app, without any other services (MySQL, Redis, etc) so these are not covered here. If you’re using docker-compose it’s “only” a matter of also adding those and exposing them over the (internal) network.

~

mikechernev/dockerised-php

This one uses docker-compose which starts up a php:fpm container and an nginx container with a network between them. Nginx is configured to pass requests for PHP files to php-fpm. This is how many articles tackle this.

~

TrafeX/docker-php-nginx

This one also uses php-fpm and Nginx but installs them from scratch and stores them in one single container (e.g. no docker-compose). Processes are controlled using supervisord

~

php:7.4-apache

Official PHP Image with Apache2 and PHP. Comes with handy utils such as docker-php-ext-configure, docker-php-ext-install, and docker-php-ext-enable. This gist is a good reference to installing many extensions

FROM php:7.4-apache

RUN apt-get update && apt-get install -y \
        libfreetype6-dev \
        libjpeg62-turbo-dev \
        libpng-dev \
    && docker-php-ext-configure gd --with-freetype --with-jpeg \
    && docker-php-ext-install -j$(nproc) gd

RUN apt-get update && apt-get install -y \
        zlib1g-dev \
        libicu-dev \
        g++ \
    && docker-php-ext-configure intl \
    && docker-php-ext-install intl

# …

RUN a2enmod rewrite

Apache Modules are enabled using the common a2enmod. Log and error files are symlinked to /dev/stdout and /dev/stderr

~

alfg/docker-php-apache

Starts with an alpine image and installs Apache and PHP into them without too much cruft. Also symlinks the log and error files on startup (not from the Dockerfile).

Also installs Composer quite cleverly by leveraging its command line options.

RUN curl -sS https://getcomposer.org/installer | php -- \
    --install-dir=/usr/local/bin --filename=composer

~

composer:1.9

This is the official Composer Image which contains PHP and Composer. Instead of installing composer.phar in your own container, you use this separate container to run composer install in, and then copy its output (e.g. the vendor folder) into the “main” container.

This is what Docker calls multi-stage builds:

With multi-stage builds, you use multiple FROM statements in your Dockerfile. Each FROM instruction can use a different base, and each of them begins a new stage of the build. You can selectively copy artefacts from one stage to another, leaving behind everything you don’t want in the final image.

It goes like this:

# COMPOSER
FROM composer:1.9.1 as composer 

COPY composer.json composer.lock /app/
RUN composer install \
    --ignore-platform-reqs \
    --no-interaction \
    --no-plugins \
    --no-scripts \
    --prefer-dist \
    --optimize-autoloader \
    -vvv

# WEBSERVER
FROM php:7.4-apache

# …

COPY --chown=www-data:www-data --from=composer /app/vendor/ /var/www/vendor/

# …

This way you don’t pollute your “main” container with Composer itself, as that is not needed to run your application.

~

Did this help you out? Like what you see?
Thank me with a coffee.

I don't do this for profit but a small one-time donation would surely put a smile on my face. Thanks!

☕️ Buy me a Coffee (€3)

To stay in the loop you can follow @bramus or follow @bramusblog on Twitter.

SSL Config Generator

Just choose the web server / web front you’re using (Apache, Nginx, HAProxy) + whether you want to support only modern, intermediate, or old versions of browsers and a proper configuration will be generated.

<VirtualHost *:443>
    ...
    SSLEngine on
    SSLCertificateFile      /path/to/signed_certificate
    SSLCertificateChainFile /path/to/intermediate_certificate
    SSLCertificateKeyFile   /path/to/private/key
    SSLCACertificateFile    /path/to/all_ca_certs

    # modern configuration, tweak to your needs
    SSLProtocol             all -SSLv2 -SSLv3 -TLSv1
    SSLCipherSuite          ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!3DES:!MD5:!PSK
    SSLHonorCipherOrder     on



    # HSTS
    Header add Strict-Transport-Security "max-age=15768000"
    ...
</VirtualHost>

Choosing modern will result in an A+ on SSLLabs. Of course a stuff like POODLE will be prevented.

Mozilla Security Recommended Web Server Configuration Files →

Zero-config development with Apache’s VirtualDocumentRoot and xip.io

# Use name-based virtual hosting.
NameVirtualHost *:80
UseCanonicalName Off
 
# ~/Sites/ vhost configuration - sends foo.bar.dev to ~/Sites/bar/foo
<VirtualHost *:80>
    VirtualDocumentRoot /Users/dave/Sites/%2/%1
 
    <Directory "/Users/dave/Sites">
        Options Indexes FollowSymLinks MultiViews
        AllowOverride All
        Order allow,deny
        Allow from all
    </Directory>
</VirtualHost>

What that VirtualDocumentRoot does is map the above company and project to the %2 and %1 variables, respectively. So whenever I surf to http://foo.bar.dev, I end up in ~/Sites/bar/foo.

Dave’s a clever man.

Zero-config development with Apache’s VirtualDocumentRoot and xip.io →

Automatic website publishing with Git, GitHub-Style

One of the things I like about GitHub is the fact that it sports a gh-pages branch. Anything you push to it, is automatically published on your GitHub subdomain http://username.github.com/projectname/.

Inspired by this GitHub publishing flow, I’ve set up a likewise method on our web servers at work: a branch which gets published automatically onto our web server whenever we push code to it. This way we can eliminate the manual (and tedious) task of FTP’ing to the server (or opening up a network share) and copying the files onto the server in order to publish.

Somewhat related: Did you know, you can also (ab)use Dropbox as a web publishing tool? Not as robust as this method, yet it might be more fitting for some of you readers.

~

The Setup

The web server at work is a Windows 2008 R2 machine running a WAMP stack to serve the (mostly static) sites. Each hosted subsite is configured a vhost and is stored in its own subfolder on disk. Apache is being run as a separate user which has limited access to the filesystem.

<VirtualHost *:80>
    ServerName subdomain.ikdoeict.be
    UseCanonicalName On
    DocumentRoot "c:/apache2/htdocs/vhosts/subdomain.ikdoeict.be/wwwroot"
    php_admin_value open_basedir "c:/apache2/htdocs/vhosts/subdomain.ikdoeict.be;c:/php/temp"
</VirtualHost>

Next to the web server we also have a private Git server running Linux to/from wich code is pushed/pulled. Repositories are accessible via HTTPS and authenticating to this server is done via an HTTP Username & Password (not via a SSH key).

Of course your mileage may vary:

  • you might be running all-linux machines with shell access enabled (in which case you might be better off with Capistrano);
  • or you might have one single server function as both the web and Git server;
  • or you might have your code stored onto a public Git server (if you’re on GitHub, just set up a Post-Recieve URL and use this script as the update script);
  • or you might skip out on the gh-pages branch and want to deploy your master;
  • etc.

Either way: adjust/skip steps where necessary to reflect your setup/preferences 😉

~

Prerequisites

Install Git on the Web Server

As it’s a Windows server I’ve installed Git for Windows, choosing to run Git and the Unix Tools from the Windows Command Prompt.

Once installed, I’ve restarted the server to get the git command available anywhere on the server.

Create new repository on the Git server

Create a (bare) repository on the Git server and make sure that is externally accessible so that you can clone it on an other machine.

~

Gitting [sic] started

Clone, Branch & Develop on your local machine

Develop your site on your machine as you’d normally do (in my case: on the master branch).

git clone https://user@gitserver/project.git .
[build site]
git add .
git commit -m 'the one true commit'
git push origin master

Also create a gh-pages branch which will be the version to be deployed to the web server.

Note: If the gh-pages branch won’t differ from the master, feel free to skip out on it and deploy the master on the web server later on.

git branch gh-pages
git checkout gh-pages
[make changes, such as adding a Google Analytics Tracking Code]
git add .
git commit -m 'changes for online version'
git push origin gh-pages

Clone on the Web Server

On the web server, clone the project into the vhost’s DocumentRoot and activate the gh-pages branch so that the web server will serve that version.

cd c:/apache2/htdocs/vhosts/subdomain.ikdoeict.be/wwwroot
git clone https://user@gitserver/project.git -b gh-pages .

Note: If you already have a clone on the web server, or mistakingly have cloned the master, run these commands instead

git branch -f gh-pages origin/gh-pages
git checkout gh-pages
git branch -d master

Be sure to verify with your favorite text editor that you’ve got the correct version. If it’s correct you’ve successfully deployed your site onto the web server. Break out the champagne, but don’t open it yet, we’ve got some more work to do.

Security Alert!

You might not know this but whenever you clone a Git project onto disk, you’ll end up with a (hidden) .git directory in the root of your project. In that directory, everything about the repository clone is stored: commits, branches, hooks, ignore files, remotes, etc.

Now that you’ve deployed the project onto the web server, that .git directory will also be present in your vhost DocumentRoot, meaning that it — and its files — are now publicly accessible via http://subdomain.ikdoeict.be/.git/

To prevent this adjust your apache config to disallow the .git folder from being accessed over HTTP.

<VirtualHost *:80>
    ServerName student.ikdoeict.be
    UseCanonicalName On
    DocumentRoot "c:/apache/htdocs/vhosts/student.ikdoeict.be/wwwroot"
    php_admin_value open_basedir "c:/apache/htdocs/vhosts/student.ikdoeict.be;c:/php/temp"
    
    <Directory "c:/apache/htdocs/vhosts/student.ikdoeict.be/wwwroot/.git">
        Order allow,deny
        Deny from all
    </Directory>
    
</VirtualHost>

~

Getting the server to fetch updates

Deploying changes

Now, how to get changes onto the web server? Make changes on your local machine as you’d normally do, and push them upstream when commited. Be sure to merge your changes in the gh-pages branch as that’s the version that’s served on the web server.

git checkout master
[make changes]
git add .
git commit -m 'changes'
git push origin master
	
git checkout gh-pages
git merge master
git push origin gh-pages

On the web server, do a git pull to get the latest version

git pull origin gh-pages

~

Automating Deployment

Prerequisite: “standalone” git pull

In order to automate deployment, git pull should run fine without any user intervention such as requiring to enter a password.

As our setup requires HTTP username + password authentication it’ll ask for the password each time I push/pull something. In order to bypass this edit .git/config so that the remote url contains the password. If you’re doing a fresh clone, you can clone the repository as https://user:pass@gitserver/project.git straightaway.

Beware though: everything is stored plaintext! If other people have access to the machine, this isn’t a good idea! Also be sure to have implemented the security step above.

PHP, do your thing!

Up until now we can deploy changes onto the server, yet it still requires us to log in to the server and manually invoke a git pull. What if we could just open up a webpage in our browser which does the updating for us?

Luckily for us, PHP has a built-in command shell_exec, which allows you to execute a command via the shell and which returns the output. Given this, it’s fairly easy to knock up a PHP script that executes a git pull

<php
    echo nl2br(shell_exec('git pull origin gh-pages 2>&1'));

Note: the 2>&1 at the end of the commmand is to route encountered errors to the output.

Add this file on your local machine to your gh-pages branch …

git checkout gh-pages
[create update.php]
git add .
git commit -m 'update script'
git push origin gh-pages

… and — for the final time — do a manual git pull on the web server.

git pull origin gh-pages

Once the file is on the web server, you can from then on update the version on the server by visiting http://subdomain.ikdoeict.be/update.php. The file will give output when done.

Note: Since Apache is running as a limited user, you must give that user R/W permissions on your DocumentRoot so that it can perform the git pull and do all basic CRUD file manipulations. You can test this by making a local change, pushing it, and visiting update.php: if no error occurs, it’s all fine and dandy!

Putting the magic in automagic

Deploying new versions goes smooth by now, but it’s not fully automated … yet.

You might not know this but Git supports hooks. Hooks are executed after a certain action is performed and are stored in .git/hooks/. For example: after a commit, you could let a hook send out a tweet with your commit message.

One of the hooks that’s interesting for us is the post-receive hook, a server-side hook which is executed after a client has performed a push. We can take this hook, and let it call the update.php script for us. This way, we just have to push, and the web server will be updated automagically.

First, create the hook by renaming the sample provided

cd .git/hooks/
mv post-receive.sample post-receive
chmod +x post-receive

Second — and most important — adjust the hook’s content so that it calls the update.php script

#!/bin/sh
curl -s http://subdomain.ikdoeict.be/update.php

Tip: If you want some actual feedback to see if everything works fine, adjust update.php so that it sends you an e-mail.

Happy deploying!

Did this help you out? Like what you see?
Consider donating.

I don’t run ads on my blog nor do I do this for profit. A donation however would always put a smile on my face though. Thanks!

☕️ Buy me a Coffee ($3)