Love My Love

Deploy Hexo to VPS By Caddy And Webhooks(Python)


There are steps to make Hexo works well with caddy, github webhooks and service script write by python3.

Why not nginx, because I know well about it, I think, and now I want to try caddy.

Why not git to deploy it? This is the way to use git for deployment. But it was outdated or I think, use webhooks and caddy maybe the pop and better way than that.

This is not as common use for webhooks, the main idear for easy to write hexo blog is follow:

One github’s repository cloned at your local system: Only write your md files at there. Two folders at VPS to clone your md files and generate by hexo for deploy.

Advantage: Write blog so easy, just write down and push to github. Generate at server, so you never need install hexo to your local system, so you can write and push anywhere have git installed Generate at server, so it will stable and faster than local.

Disadvantage: Many settings and preparations at VPS.

My System environment:

  • Local: Windows 10
  • VPS: Ubuntu 18.04 LTS

OK, let’s GO!


  1. Preparation for your host: git
  2. Preparation for your VPS: git, Node.js, Hexo, webhook scripts
  3. Install and config Caddy to your VPS

My Tree:

  • LocalMachine:
    • blog/blog_md - for blogs write.
  • VPS:
    • /var/www/blog - for generated files public
    • ~/blog - Init by hexo,we generate here.
    • ~/blog_static - git clone from repository, the repository is used for push and target webhooks.
    • ~/blog_static/blog_md - subfolder of repository, contains blog md files.
    • ~/ - listen for webhook.
    • ~/ - call by for deploy.

# 1. VPS Preparations

This step is the preparation for using github webhook.

1.1. Install git and node.js

Git is very simple and nothing to say, simply say Node.js.

Run the command below to show a list of versions available for download and install the last LTS version:

curl -o- | bash
nvm ls-remote
nvm install 10.16.0
nvm on # or nvm use 10.16.0
node -v

Return: v10.16.0

1.2. Install Hexo on VPS

npm install -g hexo-cli
npm install hexo-server -g
cd <blog folder>
hexo init

If error occur ERROR Local hexo not found in <blog folder>, run:

npm install hexo --save


npm install

If occur: found 1 moderate severity vulnerability, run:

npm audit fix --force


hexo init

1.3. Github hooks

1.3.1. Add webhook at respository where your md files placed

  • Payload URL resemble:
  • Content type: the default content type of application/json is fine.

1.3.2. Write a python

1st Way authenticate the hook conveniently (Recommend)

A easy way for enable secret: GitHub Webhook (micro) Framework It also can do anything about webhooks, but documents is so poor.

Environment Prepare:

sudo apt install python3-pip
pip3 install flask
pip3 install github_webhook

My final (run as python3):

import os
from github_webhook import Webhook
from flask import Flask

app = Flask(__name__)  # Standard Flask app
webhook = Webhook(app, endpoint="/testendpoint", secret='testsecret') # Defines '/postreceive' endpoint

@app.route("/")        # Standard Flask endpoint
def hello_world():
    return "Hello, World!"

@webhook.hook()        # Defines a handler for the 'push' event
def on_push(data):
    #print("Got push with: {0}".format(data))
    print(os.popen("sh ~/").read())

if __name__ == "__main__":
        port_number = int(sys.argv[1])
        port_number = 8888"", port=port_number, debug=False)

Key settings:

  • If code is webhook = Webhook(app, endpoint="/testendpoint", secret='testsecret'):

    • Payload URL setting is resemble:
    • Secret setting is testsecret
  • If with no secret setting:

    • webhook = Webhook(app, endpoint="/testendpoint")
  • If nothing defined yourself, code is webhook = Webhook(app):

    • Default way: /postreceive
    • Without secret verification.

2nd Way without authenticate but simple (Optional)

Without authenticate but easy, a way to study but suggust not use in Ops.

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import json
import os
import traceback

from flask import Flask, request

app = Flask(__name__)

@app.route('/webhook', methods=['POST'])
def update():
    if request.method == 'POST':
            print request.headers
            print request.json
            print os.popen("sh ~/").read()
            print traceback.format_exc()

    return json.dumps({"msg": "error method"})

if __name__ == '__main__':
        port_number = int(sys.argv[1])
        port_number = 8888"", port=port_number, debug=False)

You can enable secret by yourself(Optional choise) or jump to 1st Way: Give the abilitiy to authenticate the hook by sercet. we can modify the python code above, the core is:

import hmac

header_signature = request.META.get('HTTP_X_HUB_SIGNATURE')
signature = header_signature.split('=')
mac ='YOUR GITHUB WEBHOOK SECRET'.encode('utf-8'), request.body, hash_type).hexdigest()
if hmac.compare_digest(mac, signature):
    print os.popen("sh ~/").read()

But, the code above need test, not the final code to run, just a thought!

1.3.3. Write a shell

#!/usr/bin/env bash

cd ~/blog_static
git pull
rm -rf ~/blog/source/_posts/*
cp -rp ~/blog_static/blog_md/* ~/blog/source/_posts/

cd ~/blog
hexo clean
hexo generate
rm -rf /var/www/blog/*
cp -rp ~/blog/public/* /var/www/blog/

1.3.4. Run in background:

nohup python &

Don’t warry, you can read logs in file nohup where located.

If your server’s environment is not suitable, run the command follow:

sudo apt install python-pip
pip install request flask

I suggest test before run in background,you can run the command below to assess the return lines:


# 2. Install and config Caddy to your VPS

2.1. Directory and permissions prepare



Main idea: Give website folder appropriate ownership and permission, so if git pushed, python script can catch the webhook event and load shell script to pull, move etc.

(If your VPS not setted default)Set up the user, group, and directories that will be needed:

sudo groupadd -g 33 www-data
sudo useradd \
  -g www-data --no-user-group \
  --home-dir /var/www --no-create-home \
  --shell /usr/sbin/nologin \
  --system --uid 33 www-data

Create caddy folder and give it appropriate ownership and permissions:

sudo mkdir /etc/caddy
sudo chown -R root:root /etc/caddy
sudo mkdir /etc/ssl/caddy
sudo chown -R root:www-data /etc/ssl/caddy
sudo chmod 0770 /etc/ssl/caddy

Place your caddy configuration file (“Caddyfile”) in the proper directory and give it appropriate ownership and permissions:

sudo cp /path/to/Caddyfile /etc/caddy/
sudo chown root:root /etc/caddy/Caddyfile
sudo chmod 644 /etc/caddy/Caddyfile

Create the home directory for the server and give it appropriate ownership and permissions:

sudo mkdir /var/www
sudo chown www-data:www-data /var/www
sudo chmod 755 /var/www

Let’s assume you have the contents of your website in a directory called ‘blog’. Put your website into place for it to be served by caddy:

sudo mkdir /var/www/blog
sudo chown -R <youruser>:<yourgroup> /var/www/blog
sudo chmod -R 755 /var/www/blog

2.2. Config Caddyfile

DIffer from other service, for caddy you can make config file first to ensure which plugin is necessary, after config the file, caddy install option will also becoming your mind, you can study how to set it by caddy offical doc:

/etc/caddy/Caddyfile may resembles below: {
    root /var/www/blog
    index index.html
    # minify
    log /var/www/access.log {
        rotate_size 50  # Rotate a log when it reaches 50 MB
        rotate_age  90  # Keep rotated log files for 90 days
        rotate_keep 20  # Keep at most 20 rotated log files
        rotate_compress # Compress rotated log files in gzip format
    limits {
        header 100KB
        body   /download 100MB
    errors /var/www/error.log {
        # 404 404/index.html
        rotate_size 50  # Rotate a log when it reaches 50 MB
        rotate_age  90  # Keep rotated log files for 90 days
        rotate_keep 20  # Keep at most 20 rotated log files
        rotate_compress # Compress rotated log files in gzip format

2.3. Install via script

This step requires root privileges, so:

curl | bash -s personal http.cache,http.minify

Enable Caddy as service:


If you assume the following:

  • that you want to run caddy as user www-data and group www-data
  • caddy binary in the system wide binary directory: /usr/local/bin/caddy
  • your caddy configuration file (“Caddyfile”) in the proper directory /etc/caddy/Caddyfile
  • your caddy tls(Letsencrypt-issued certificates) want to write in: /etc/ssl/caddy

please preceed:

# 从 github 下载 systemd 配置文件
sudo curl -s -o /etc/systemd/system/caddy.service
sudo systemctl daemon-reload        # 重新加载 systemd 配置
sudo systemctl enable caddy.service # 设置 caddy 服务自启动
sudo systemctl status caddy.service # 查看 caddy 状态


edit /etc/systemd/system/caddy.service to set your right user, group, and so on.

# 3. Some Tips:

If you use caddy, sometimes, web is blank, also viewed by source, maybe, your Caddyfile have something wrong or your vps cannot support some feature well, so, comment out line by line and test. For me, I commented minify, ext while the blank page occured, and it fixed.

About Github hooks, if python and shell is allright, but still cannot works well, try to change github setting of webhooks, set Payload URL as http://your-url not https.


Thanks for