Hardware FixRecommendedDevice not working? Your driver may be the problemCheck updates for common hardware issues.Fix DriversHalloween DealsAmazon USHalloween week deals: useful picks before month-endAmazon US: current deals, useful picks and tech finds.Check DealsWindows FixRecommendedWindows errors stealing your time? Find the fix fastScan stability, cleanup and performance issues.Fix Now×
Skip to content
World desk12 min

How to Deploy a Jekyll Site with Git Hooks on a Linux VPS

A practical, security-conscious guide to pushing a Jekyll site to a Linux VPS and rebuilding it automatically with a bare Git repository and post-receive hook.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

The simplest self-hosted workflow is a bare Git repository owned by a dedicated SSH account. You push to that repository, its executable post-receive hook checks the branch, builds the site with Bundler and Jekyll in a temporary checkout, and copies the generated files into the directory served by Nginx or Apache. The live web root never needs to contain Git metadata or your source checkout.

This guide shows a production-conscious setup: isolated permissions, dependency locking, branch validation, temporary build directories, serialized deployments, logging, and preservation of the previous site when a build fails.

As an Amazon Associate I earn from qualifying purchases.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

How the deployment works

A normal Git repository has a working tree. A bare repository does not; it stores commits and references only, which makes it suitable as an SSH push endpoint. Git runs a server-side post-receive hook after accepted reference updates. Git documents that hooks without the executable bit are ignored and that this hook is invoked by git-receive-pack when a push updates references (Git hooks documentation).

  1. Your workstation runs git push deploy main.
  2. SSH invokes Git on the VPS as the restricted deploy user.
  3. The bare repository accepts the objects and updates its references.
  4. The hook reads the updated references and proceeds only when the intended branch, such as refs/heads/main, changed.
  5. The hook clones the bare repository into a unique temporary directory, installs the Gemfile dependencies, and runs bundle exec jekyll build.
  6. Only a successful build is synchronized into the public directory.
  7. Nginx or Apache serves that generated directory.

Jekyll’s documented automated-deployment sequence uses the same pattern: initialize a bare repository, clone $GIT_DIR to a temporary checkout, run BUNDLE_GEMFILE=$GEMFILE bundle install, then run BUNDLE_GEMFILE=$GEMFILE bundle exec jekyll build -s $TMP_GIT_CLONE -d $PUBLIC_WWW (Jekyll automated deployment documentation).

#1 Best Overall
Sale
TOZO A1 Wireless Earbuds Bluetooth 5.3 Light Weight in Ear Headphones | IPX5 Waterproof,2 Mics for AI Calls,Immersive Premium Sound Bass Headset with Charging Case,32 EQ via App Black
  • [Ultra-Lightweight Ear Buds Designed for Small Ears] Each earbud weighs only 3.7g and features a compact, ergonomic in-ear design made especially for small ears. Secure, low-profile, and comfortable for workouts, all-day wear or overnight sleeping.The ultra-slim profile sits flush in your ear with no hard edges poking or rubbing, so you can even sleep on your side without discomfort.
  • [Immersive Stereo Sound with TOZO OrigX Technology] TOZO OrigX tuning delivers clear vocals, balanced mids, and natural stereo sound for music, podcasts, and videos.
  • [Long Battery Life for Daily Use] Get up to 7 hours of playtime on a single charge, with up to 32 hours total using the charging case—ideal for workdays, commuting, and extended listening sessions.
  • [Bluetooth 5.3 & Stable Connection] Bluetooth 5.3 provides fast pairing, stable wireless performance, and reduced dropouts as you move around home or office.
  • [Deep Bass with Clear Vocals] High-performance drivers produce punchy bass while keeping vocals clean and detailed for everyday listening.

Decide the layout before installing anything

Keep the three types of data separate. The bare repository and build workspaces belong outside the public document root; only generated static files should be readable by the web server.

Purpose Example path Owner and access
Bare Git push endpoint /home/deployer/myrepo.git Owned and writable by deployer; never publicly served
Temporary source and build directories /tmp or a private directory under the deploy user’s home Writable only by the deploy process; removed after each run
Generated site /var/www/myrepo Deploy user writes; Nginx or Apache reads
Hook log /home/deployer/deploy.log or the system journal Readable by administrators, not the web server

Using a fixed public path lets the virtual host remain unchanged while each successful push replaces the generated files. If you prefer release directories and symlink swaps, implement that as a separate release-management layer; the hook below uses a staging directory followed by a successful-only synchronization.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Prerequisites on the VPS

  • A Linux VPS with a DNS name or address reachable by SSH.
  • A non-root deploy account, here called deployer.
  • Git, a supported Ruby runtime, Bundler, and Jekyll dependencies declared by the site’s Gemfile.
  • Nginx or Apache, plus rsync for the successful-only file handoff used in this example.
  • A workstation containing the Jekyll site’s Git repository and an SSH key.

Package names and Ruby versions vary by distribution. The following commands are an Ubuntu/Debian-style example; use the current packages supplied by your distribution rather than copying an obsolete Ruby version from an older tutorial. A 2018 Ubuntu 16.04 walkthrough demonstrates the same general method but is not a current package guide (DigitalOcean’s Git-hook tutorial).

sudo apt update
sudo apt install git ruby-full build-essential nginx rsync

For a new project, keep the Jekyll and plugin versions in Gemfile and commit Gemfile.lock. Build as the deploy user, not as root, so a malicious or broken dependency cannot write arbitrarily across the system.

Create and restrict the deploy account

Create a dedicated account whose only job is receiving deployments. Do not reuse your personal administrator account or the web-server account.

sudo adduser --disabled-password --gecos '' deployer
sudo install -d -m 700 -o deployer -g deployer /home/deployer/.ssh
sudo install -m 600 -o deployer -g deployer /dev/null /home/deployer/.ssh/authorized_keys

Append only the public key that should deploy this site to /home/deployer/.ssh/authorized_keys. Disable password login for the account in your SSH policy where appropriate. After confirming key-based access, consider assigning git-shell as the account’s login shell or using a forced command. Test the restriction carefully: the account must still be able to receive Git pushes, but it should not provide an interactive shell. A forced-command design is more restrictive but requires matching the SSH command Git sends.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Rank #2
Sale
Smart Watch for Men Women Compatible with iPhone Samsung Android Phone 1.69 inch Full Touch Screen IP68 Waterproof Bluetooh Fitness Tracker Heart Rate/Sleep Monitor (Black)
  • 【IP68 Waterproof Multifunctinal Smart Watch】Torjalph fitness activity tracker smart watch is IP68 waterproof technology ,you can wear it when swimming,handwashing,it's rain-proof which means that you perform multiple exercise modes outside without worrying about sweat or rain corroding your fitness smart watch,and this watch equipped with a 230 mAh large capacity battery magnetic charging ,and this sports activity tracker watch can be used 5-7 days If fully charged.
  • 【Larger 1.83'' Full Screen Watch & Multi-dial Selection】1.83 inch full HD color touch screen smart watch for men / women provides excellent 240*284 resolution image quality ,This Bluetooth smart watch offers a better touch experience and remarkable immersion and ,more than 200 watch faces for you to choose from App"Da Fit ", you can change the dial style according to your mood to wear keeping the world on your wrist
  • 【Accurate Activity Tracker Smart Watch 】sports watch activity tracker uses sweat and dust resistant breathable silicone strap, our Fitness Tracker Smart Watch for Android iOS Phones supports 12 sports modes,walking,running,bicycle,swimming,badminton,basketball,football,rope skipping. This bluetooth calls smart watch for men women is lightweight and stylish, bringing more convenient and relaxing sports experience while also providing more protection.
  • 【Your 24 Hour Personal Daily Health Monitor】Fitness watch for women men full touch screen IP67 waterproof bluetooth smartwatch for Android iOS Phones with heart rate blood pressure monitor multifunction Andriod smart watch with steps counter sleep monitor sports fitness watch, monitoring blood oxygen saturation allows you to understand your physical condition in real time to track fitness progress ,to know your health status in time.
  • 【More Functions Bluetooth Call Smart Watch】This fitness activity smart watch full screen for iPhone and Android phones includes a bunch of new features including:The bluetooth smart watch can hang up the phone, receive and display WhatsAPP /Text /Facebook /Twitter /Messenger reminder.Support sedentary reminder, alarm clock, stopwatch, timer,weather information,music control, remote camera control compatible with Android 5.0 and above or iOS 9.0 and above smartphones.

The Git hooks manual notes that hooks normally live in $GIT_DIR/hooks (or the configured core.hooksPath), that Git changes to the repository directory before invoking them, and that variables including GIT_DIR are exported (Linux githooks(5) manual). The hook below therefore captures its repository directory explicitly and unsets a possible inherited GIT_WORK_TREE.

Create the bare repository and output directory

Run these commands as the deploy user. The bare repository is the remote named deploy on your workstation.

sudo -iu deployer
mkdir -p "$HOME/myrepo.git"
cd "$HOME/myrepo.git"
git --bare init
exit

Create the output directory and give the deploy process write access while allowing the web server to read it. On Debian-family systems, Nginx and Apache commonly read as www-data; verify the actual user for your distribution.

sudo install -d -o deployer -g www-data -m 0750 /var/www/myrepo
sudo chmod g+rx /var/www/myrepo

If the web server cannot traverse a parent directory, grant execute permission on the parent directories without making the repository public. Do not put myrepo.git, .git, Bundler caches, or temporary source files below /var/www/myrepo.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Install the post-receive hook

Create /home/deployer/myrepo.git/hooks/post-receive with this content. It deploys only main, builds in a unique directory, serializes pushes with flock, logs output, and leaves the existing site untouched if dependency installation or the Jekyll build fails.

#!/usr/bin/env bash
set -euo pipefail

export PATH=/usr/local/bin:/usr/bin:/bin
unset GIT_WORK_TREE

REPO_DIR="$(pwd)"
PUBLIC_WWW=/var/www/myrepo
LOG_FILE="$HOME/deploy.log"
LOCK_FILE="$HOME/deploy.lock"
TMP_ROOT=""
DEPLOY=false

while IFS=' ' read -r oldrev newrev ref; do
  if [[ "$ref" == "refs/heads/main" ]]; then
    DEPLOY=true
  fi
done

if [[ "$DEPLOY" != true ]]; then
  exit 0
fi

exec 9>"$LOCK_FILE"
flock 9

TMP_ROOT="$(mktemp -d "$HOME/jekyll-build.XXXXXX")"
TMP_SOURCE="$TMP_ROOT/source"
STAGING="$TMP_ROOT/public"
mkdir -p "$STAGING"
trap 'rm -rf "$TMP_ROOT"' EXIT

{
  printf 'n[%s] deploying mainn' "$(date -u +%FT%TZ)"
  git clone --quiet "$REPO_DIR" "$TMP_SOURCE"
  cd "$TMP_SOURCE"
  test -f Gemfile
  BUNDLE_GEMFILE="$TMP_SOURCE/Gemfile" bundle install
  BUNDLE_GEMFILE="$TMP_SOURCE/Gemfile" bundle exec jekyll build 
    --source "$TMP_SOURCE" 
    --destination "$STAGING"
  rsync -a --delete "$STAGING"/ "$PUBLIC_WWW"/
  printf '[%s] deployment completedn' "$(date -u +%FT%TZ)"
} >>"$LOG_FILE" 2>&1

Install and test the hook’s permissions:

sudo -iu deployer
chmod +x "$HOME/myrepo.git/hooks/post-receive"
ls -l "$HOME/myrepo.git/hooks/post-receive"

The mode must include an executable bit. A hook file that exists but is not executable is silently ignored by Git. Keep the hook owned by deployer and writable only by that account:

Rank #3
New Bee Guitar Cable 10ft Electric Instrument Bass AMP Cord
  • [Low Noise High Fidelity] New bee guitar cord with 22AWG oxygen-free-copper center conductors provides enhanced signal transfer, the PVC insulation material and the high-density stranded copper double shield provide noise rejection for quiet operation
  • [Ideal for Choice] New bee 1/4 straight and right angle metal connectors, ensures a secure connection in any instrument, pedal, or amp jack. L shape great fro on stage guitar stands
  • [Wide Compatible] Suitable for mixer, power amplifier, microphone, effector, decoder, equalizer, electoronic organ, electric guitar, electric bass and other electronic equipments. Widely used in the installation of smart home, karaoke speaker, professional stage audio, etc
  • [Easy Disassemble] Making incidental repairs quick and easy! Very nice, flexible cable
  • [Dont Hesite to Order] 24 hours customer services and professional technology team are standing by
chmod 750 "$HOME/myrepo.git/hooks/post-receive"
exit

post-receive receives one space-separated line for each updated reference: old object ID, new object ID, and the full ref name. The loop consumes every line before building, so a push containing several refs still produces at most one deployment. The branch comparison uses a literal ref name; it never interpolates an untrusted branch name into a shell command. If your release branch is different, change the comparison deliberately and keep the full refs/heads/… form.

Why the hook uses staging, locking and cleanup

Staging protects the current site

Jekyll writes into STAGING, not directly into the live directory. A missing gem, malformed configuration, or Liquid error causes the script to exit before rsync runs, leaving the previous generated site available. The final rsync -a --delete makes the public tree match the successful build, including removing files deleted from the repository.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

The lock handles concurrent pushes

Two pushes can arrive close together. Without serialization, both builds could copy files into the same destination in an unpredictable order. The file-descriptor lock allows one hook to build at a time. Ensure flock is installed on your distribution, or replace it with another locking mechanism supported by the operating system.

The trap removes secrets and source files

The temporary clone can contain private content, the Gemfile, and Git metadata. The exit trap removes it on success, a build error, or an interrupted shell. The private log may still contain command output, so protect its permissions and rotate it.

Dependency resolution is reproducible only when you lock it

Commit Gemfile.lock and pin the Ruby and gem versions your site supports. The hook intentionally runs bundle exec, which selects the versions resolved for the project instead of whichever globally installed Jekyll happens to be first in PATH. If the project requires a particular Ruby manager or native library, install and configure it for the deploy account before the first push.

Configure Nginx or Apache

Point the virtual host’s document root at the generated directory, not at the bare repository. A minimal Nginx server block is:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Rank #4
BSIMB 15.6" Digital Family Calendar & Chore Chart, No Subscription Fees
  • Digital Family Calendar: BSIMB digital calendar wall touch screen puts an end to your chaotic life. Designed for busy families, the digital calendar keeps you organised and in step with your family. Assign different colours and tasks to each family member to make family scheduling easier and more intuitive.
  • Easy Setup and Auto-Sync: Plug in, connect to WLAN and auto-syn your electric calendar in minutes. Digital wall calendar compatible with Google calendar, iCloud calendar, Outlook calendar, Cozi calendar and Yahoo calendar. Add events directly on the device or through the ecalendar app. Electronic calendar stays current whether you're at work or traveling—no manual updates needed.
  • All-In-One Home Assistant: Smart calendar builds habits and encourage independence with personalised chores. Display daily meal plan to reduce the daily ‘What's for dinner?’ stress. To-do lists keep track of everything you want. Turning chores into valuable achievements with stars and rewards, making life easier and more enjoyable.
  • Manage Anytime Anywhere: BSIMB electronic calendar for family stay connected with the powerful ecalendar app. Access and manage your digital wall calendar anytime, anywhere with free features like stars & rewards, photo screensaver, chore charts, meals plans, to-do lists etc. Electronic planner paid features like Magic Import tailor advanced tools for users with special needs.
  • Stay in Touch with Loved Ones: Share photos or videos from your phone to electronic wall calendar with app, instantly update your life moment, and keep in touch with your family, especially those who are far away; you can also surprise your smart calendar digital wall by sending him a wish card; it's also great for keeping tech-illiterate elders in touch with the youngsters.
server {
    listen 80;
    server_name example.com www.example.com;
    root /var/www/myrepo;
    index index.html;

    location / {
        try_files $uri $uri/ =404;
    }
}

Place the block in the distribution’s enabled-sites location, validate it, and reload Nginx using the service manager. Apache uses the same principle: set the virtual host’s DocumentRoot to /var/www/myrepo and grant the web-server user read and directory-traverse access. Add HTTPS separately with the certificate tooling appropriate to your distribution; TLS is not provided by the Git hook.

Before testing a push, verify that the configured domain resolves to the VPS, the web server can read an existing index.html, and no repository path is reachable over HTTP. A request for a missing file should return the status your chosen server configuration specifies rather than exposing a directory listing.

Connect your local repository and deploy

From the workstation, add an SSH remote. The tilde is interpreted in the deploy user’s home directory by the remote Git command.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
git remote add deploy [email protected]:~/myrepo.git
git push deploy main

Use the branch accepted by your hook if it is not main. A successful push reports the remote reference update; the hook’s build output is written to the deploy user’s log. Check the site and the log:

ssh [email protected] 'tail -n 80 ~/deploy.log'
curl -I https://example.com/

If you need a first-run diagnostic, temporarily run the hook’s commands manually as deployer in a private directory. Do not run the build as root to bypass a permission problem; fix ownership, PATH, Ruby, or Bundler configuration instead.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Security and operational checklist

  • Use SSH keys and a dedicated account; disable password authentication for that account when your access policy allows.
  • Restrict the account with git-shell or a carefully tested forced command.
  • Keep the bare repository, .git data, Gem caches, logs, and temporary checkouts outside the public root.
  • Pin Ruby and gem dependencies in Gemfile and Gemfile.lock; build as the deploy user.
  • Validate the exact ref before deploying and never execute a branch name as shell input.
  • Give only the deploy process write access to the generated tree; give the web server read and traverse access.
  • Review hook logs and rotate them so a long-running site does not fill the deploy user’s disk.
  • Keep the previous generated site until the replacement build succeeds.
  • Back up the bare repository or mirror it elsewhere; it is the source of deployments, not a substitute for backups.
  • Test the hook after Ruby, Bundler, Jekyll, Nginx, or operating-system upgrades.

Common failures and precise fixes

The push succeeds but nothing is rebuilt

Check that the pushed ref is exactly refs/heads/main, the hook is at myrepo.git/hooks/post-receive, and its mode includes execute permission. Inspect ~/deploy.log. Git’s hook directory and invocation rules are described in the official documentation.

The hook reports “command not found”

Hooks run with a non-interactive environment and may not read your shell profile. Install the required tools for the deploy account and set an explicit PATH, or use absolute executable paths after confirming them with command -v. Verify that Bundler and Jekyll are available to the same Ruby installation.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Bundler cannot install a gem

Read the first native-extension or version error in the log. Install the distribution’s development headers and compiler required by that gem, confirm the locked Ruby version is supported, and rerun bundle install as deployer. Do not delete the lockfile merely to silence a dependency conflict; resolve the version incompatibility intentionally.

Jekyll builds locally but fails on the VPS

Compare Ruby, Bundler, Jekyll, plugin, and operating-system versions. Check case-sensitive file names, missing environment variables, and files excluded by _config.yml. Run bundle exec jekyll build from the temporary source as the deploy user to reproduce the hook’s environment.

The new files exist but Nginx returns 403

Check ownership and mode on every parent directory as well as the generated files. The web-server user needs directory traverse permission and read permission on files. Confirm the virtual host’s root points to /var/www/myrepo, then validate and reload the server configuration.

A failed build leaves a partially updated site

The supplied hook builds in staging and synchronizes only after Jekyll succeeds. If a partial update still occurs, inspect whether an administrator or another process is writing to the public directory, whether rsync was interrupted, and whether concurrent hooks bypassed the lock. Restore the last known-good generated files from your backup or a fresh successful build.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Two pushes deploy in the wrong order

Confirm that flock is available and that every hook invocation opens the same lock file. A serialized lock prevents overlap, but the later accepted push is still the version that should eventually be live. For strict ordering and audit trails, move the build to a CI or queue-based system.

When a direct hook is the right choice

A post-receive hook is efficient when one repository, one VPS, and one production environment are enough. It has few moving parts: the VPS receives the push and performs the build. The trade-off is that production now owns the compiler toolchain, gem cache, secrets, logs, concurrency policy, and rollback procedure.

Approach Where the build runs Strengths Costs and risks
Direct post-receive hook The VPS Simple, fast feedback, no separate runner Build failures and toolchain drift affect production; observability and rollback are your responsibility
CI service A hosted or self-hosted runner Clean build isolation, logs, approvals, and repeatable artifacts Requires secrets, runner configuration, artifact transfer, and a deployment step
Webhook-based deployer A separate deployment service or script Can coordinate several repositories or environments Needs authenticated webhook handling, replay protection, and another service to operate
SSH/Rake script or scp Usually the workstation or a remote shell Useful for small or one-off sites Less consistent than a server-side build; credentials and local tool versions can vary
Static publishing platform Managed infrastructure Hosted builds, certificates, logs, and scaling Less control over runtime, network, and deployment policy

Jekyll lists CI services, webhook deployments, static publishers, Rake or SSH scripts, and scp as alternatives. Its documentation describes these services as running a script when a repository receives a commit (Jekyll deployment options). Compare alternatives by build isolation, rollback behavior, secret handling, branch controls, concurrent-deployment handling, observability, and whether the VPS or a hosted runner performs the build.

Final verification before calling it production-ready

  1. Push a harmless change to main and confirm the hook log records the commit and a successful build.
  2. Push a change that deliberately causes a Jekyll error; confirm the command fails and the previously served page remains available.
  3. Push a non-production branch; confirm no deployment occurs.
  4. Verify the web server serves generated HTML, CSS, JavaScript, images, and clean URLs from the configured root.
  5. Inspect permissions to ensure the web server cannot read the bare repository or temporary source.
  6. Test two quick pushes and confirm the lock serializes builds.
  7. Back up the bare repository and document how to rebuild the site manually if the VPS must be replaced.

Once these checks pass, the deployment contract is straightforward: an accepted push to the selected branch creates a fresh Jekyll build, and only that successful build becomes visible at the web server’s document root.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

Leave a Reply

Your email address will not be published. Required fields are marked *

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

More from the Wire

  1. World desk4 min
    How to Spot an AI Voice Scam Before Sending MoneyDon’t rely on how a caller sounds. Pause, call back through a known number, and verify the emergency with another trusted person before sending money.
  2. Mountain View desk4 min
    Google’s SynthID Detector: How to Check AI-Generated Images, Video and AudioGoogle’s SynthID Detector looks for an embedded watermark in supported images, video and audio. Here is what its results do—and do not—show.
  3. Redmond desk20 min
    How to create a link to File or Folder in Windows 11Windows 11 gives you several ways to point to a file or folder without moving or duplicating it. You can create a desktop shortcut,…
Recommended PC Tool
Recommended PC Tool
Outdated Drivers Are Slowing You DownFree scan - exact matches
Windows Errors? Fix Them Before They SpreadFree repair scan

Two free Windows tools

One Free Minute Could Fix That PC

Before you go - each of these free tools takes about a minute and tackles what quietly slows a Windows PC down.

Special offer. View Outbyte info, uninstall instructions, EULA, and Privacy Policy.