Preface
Previously, I introduced a VPS deployment method for Hexo blogs. However, although that method created a git user, it granted excessive permissions, which could potentially invite unnecessary security risks. Therefore, this article introduces a more secure VPS deployment approach. The main feature is creating a dedicated user blog with restricted permissions—limited to Git operations using SSH keys only, with no interactive login capability. Using this highly restricted user account to perform hook synchronization operations will be more secure and reliable. This serves as a supplement to the previous method.
I. Creating a Restricted User and Directories on the Server
The following commands are executed on a Debian 12 server (with sudo privileges).
- Install dependencies and create the site directory
sudo apt update
sudo apt install -y git
sudo mkdir -p /var/www/hexo
- Create a restricted user blog for Git operations only (disable password, use git-shell to restrict interaction)
sudo adduser --disabled-password --gecos "" --shell /usr/bin/git-shell blog
- Grant the blog user ownership of the site directory (or at least write permissions)
sudo chown -R blog:blog /var/www/hexo
sudo chmod -R u=rwX,g=rX,o=rX /var/www/hexo
If your web server (such as nginx) requires read permissions, the above permissions already cover this. If group collaboration is needed, you can change the group to www-data and grant group read permissions.
- Allow SSH key authentication only, disable interaction, disable port forwarding, etc. (enhanced security)
Create or edit /etc/ssh/sshd_config.d/blog.conf:
Match User blog
PasswordAuthentication no
AuthenticationMethods publickey
X11Forwarding no
AllowTcpForwarding no
PermitTTY no
Then reload SSH:
sudo systemctl reload ssh
II. Configure SSH Public Key for the blog User
Generate a key pair on your development machine (local) if you don’t already have one, and add the public key to the blog user on the server.
On local machine (development machine):
# Skip if you already have one
ssh-keygen -t ed25519 -C "hexo-deploy"
ssh-copy-id -i ~/.ssh/id_ed25519.pub blog@your.server.com
Or manually append the public key content to the server:
sudo -u blog mkdir -p ~blog/.ssh
sudo -u blog chmod 700 ~blog/.ssh
sudo -u blog bash -c 'cat >> ~blog/.ssh/authorized_keys'
sudo -u blog chmod 600 ~blog/.ssh/authorized_keys
III. Create a Bare Repository to Receive Pushes
- Create a bare repo as the blog user:
sudo -u blog mkdir -p ~blog/repos
sudo -u blog git init --bare ~blog/repos/hexo.git
- (Optional) Deploy only a specified branch (e.g., main), which will be used in the hook later.
IV. Write a post-receive Hook: Synchronize to /var/www/hexo Upon Receiving a Push
The hook runs with blog user identity, so blog must have write permissions to /var/www/hexo (already configured earlier).
Create the hook file and grant execution permissions:
sudo -u blog tee ~blog/repos/hexo.git/hooks/post-receive >/dev/null <<'HOOK'
#!/bin/sh
set -eu
TARGET="/var/www/hexo"
GIT_DIR="/home/blog/repos/hexo.git"
BRANCH="main" # The branch name you want to deploy (main or master), remember to keep it consistent with your Hexo config
mkdir -p "$TARGET"
while read oldrev newrev ref
do
if [ "$ref" = "refs/heads/$BRANCH" ]; then
echo "Deploying $BRANCH to $TARGET ..."
git --work-tree="$TARGET" --git-dir="$GIT_DIR" checkout -f "$BRANCH"
git --work-tree="$TARGET" --git-dir="$GIT_DIR" clean -fdx
chmod -R u=rwX,g=rX,o=rX "$TARGET"
echo "Done."
else
echo "Push to $ref (ignored, only deploy $BRANCH)."
fi
done
HOOK
sudo -u blog chmod +x ~blog/repos/hexo.git/hooks/post-receive
This solution does not leave a .git directory in /var/www/hexo (because it’s checked out from the bare repository in work-tree form), making it suitable for serving static files directly via nginx/Apache.
Next, you can configure nginx to point the site root to /var/www/hexo, and your Hexo blog will be publicly accessible. All subsequent steps remain exactly the same as before.