Just some quick notes on this - as everytime I setup a new server I forget some steps!

  1. Stick the Hugo executable somewhere on the server.
  2. Use the following PHP deploy script from here, published below in case it ever goes offline.
	 * Automated deploy from GitHub
	 * https://developer.github.com/webhooks/
	 * Template from ServerPilot (https://serverpilot.io/community/articles/how-to-automatically-deploy-a-git-repo-from-bitbucket.html)
	 * Hash validation from Craig Blanchette (http://isometriks.com/verify-github-webhooks-with-php)

	// Variables
	$secret = getenv('GH_DEPLOY_SECRET'); # Paul note: Best to set environment variable - avoid setting here.
	$repo_dir = '/home/example/git/examplerepo';
	$web_root_dir = '/home/example/public_html';
	$rendered_dir = '/public';
	$hugo_path = '/usr/local/bin/hugo';

	// Validate hook secret
	if ($secret !== NULL) {

		// Get signature
		$hub_signature = $_SERVER['HTTP_X_HUB_SIGNATURE'];

		// Make sure signature is provided
		if (!isset($hub_signature)) {
			file_put_contents('deploy.log', date('m/d/Y h:i:s a') . ' Error: HTTP header "X-Hub-Signature" is missing.' . "\n", FILE_APPEND);
			die('HTTP header "X-Hub-Signature" is missing.');
		} elseif (!extension_loaded('hash')) {
			file_put_contents('deploy.log', date('m/d/Y h:i:s a') . ' Error: Missing "hash" extension to check the secret code validity.' . "\n", FILE_APPEND);
			die('Missing "hash" extension to check the secret code validity.');

		// Split signature into algorithm and hash
		list($algo, $hash) = explode('=', $hub_signature, 2);

		// Get payload
		$payload = file_get_contents('php://input');

		// Calculate hash based on payload and the secret
		$payload_hash = hash_hmac($algo, $payload, $secret);

		// Check if hashes are equivalent
		if (!hash_equals($hash, $payload_hash)) {
		    // Kill the script or do something else here.
		    file_put_contents('deploy.log', date('m/d/Y h:i:s a') . ' Error: Bad Secret' . "\n", FILE_APPEND);
		    die('Bad secret');


	// Parse data from GitHub hook payload
	$data = json_decode($_POST['payload']);

	if (empty($data->commits)){
		// When merging and pushing to GitHub, the commits array will be empty.
		// In this case there is no way to know what branch was pushed to, so we will do an update.
		$commit_message .= 'true';
	} else {
		foreach ($data->commits as $commit) {
			$commit_message .= $commit->message;

	if (!empty($commit_message)) {

		// Do a git checkout, run Hugo, and copy files to public directory
		exec('cd ' . $repo_dir . ' && git fetch --all && git reset --hard origin/master');
		exec('cd ' . $repo_dir . ' && ' . $hugo_path);
		exec('cd ' . $repo_dir . ' && cp -r ' . $repo_dir . $rendered_dir . '/. ' . $web_root_dir);

		// Log the deployment
		file_put_contents('deploy.log', date('m/d/Y h:i:s a') . " Deployed branch: " .  $branch . " Commit: " . $commit_message . "\n", FILE_APPEND);

  1. Create webhook and secret in Github repository.
  2. Set GH_DEPLOY_SECRET as an environment variable for above script to use.
  3. Skip for public repo, else create SSH key if needed ssh-keygen -t ed25519 -C "githubemail@example.com" and import key into your account on Github.
  4. May need eval "$(ssh-agent -s)" and tell SSH to use the key ssh-add /path/to/key.
  5. Clone the repo to your web server by running in a suitable directory git clone git@github.com:you/repo.git don’t worry about username will authenticate with SSH key.
  6. CD into the repo directory make sure you can run git fetch --all.
  7. If this fails, may need to create personal access token in Github and specify it as part of the URL in .git/config inside the repo directory.
  8. May also need to run git config --global --add safe.directory /home/example/git/examplerepo.
  9. Check line 69 of deployment script and make sure origin/master is correct for your branch.
  10. Make sure PHP process has execute permissions on Hugo, and has read/write access to repo dir and the web server public_html directory, as once built into repodir/public it will need to be copied to public_html.
  11. If you can see this post, it worked!

Might write this up as more of an organised general tutorial one day, in the meantime feel free to drop any comments below.