The Plesk Git extension is the cheapest CI/CD pipeline on a hosting box — a webhook on the repo, a deploy action on the panel, and git push becomes the deployment. No GitHub Actions runner, no SSH wrapper script, no rsync cron. It ships free with Plesk Web Pro and Web Host (and as a paid add-on under Web Admin), and most operators never bother to set it up properly for clients.
This guide covers the agency setup — pulling from a private GitHub repo on push, running an npm or composer build, and writing the result to httpdocs while keeping the working tree out of the document root. It takes about 20 minutes per subscription the first time, and three minutes per repo after that.
Prerequisites
You need:
- Plesk Obsidian 18.0.40+ with the Git extension installed. Web Pro and Web Host include it; Web Admin needs the Developer Pack add-on or a per-subscription Git extension purchase.
- The subscription's system user with Access to the server over SSH set to /bin/bash (Subscription → Web Hosting Access). Deploy actions run as this user —
/bin/falsewill silently skip them. - Outbound HTTPS (port 443) from the Plesk box to your Git provider. For self-hosted GitLab on a private network, also open SSH (port 22) to the GitLab host.
- A repo with a
httpdocs/directory in it, or a build step that emits tohttpdocs/. Pushing a Laravel or Next.js project root straight at the document root exposes.envandnode_modules/to the public — see the common mistakes below.
Step 1 — Decide local vs remote repository
Plesk gives you two repo modes per subscription:
- Pull from remote repository. Plesk clones GitHub/GitLab/Bitbucket and re-pulls on every webhook hit. The canonical repo lives off-server. Use this for agency work — the developer commits where they already work, the panel is just the deployment target.
- Initialize empty local repository. Plesk hosts the repo on the server itself; the developer adds
ssh://<user>@<server>/var/www/vhosts/<domain>/git/<reponame>as a Git remote and pushes there. Use this when the client doesn't have a GitHub account, or when the developer wants the panel as the single source of truth.
The rest of this guide assumes the remote-pull pattern. The local-repo flow is similar but skips Step 3.
Step 2 — Create the repository in Plesk
In Plesk, open Subscriptions → example.com → Git → Add Repository. Set:
- Repository URL:
git@github.com:agency/clientsite.git(SSH form, not HTTPS — deploy keys are easier to scope than personal access tokens). - Repository name: the directory name Plesk will use under
/var/www/vhosts/<domain>/git/. Keep it short and ASCII; this name appears in the webhook URL. - Branch:
mainfor production. Create a second repo on astagingbranch later if you want a staging URL. - Deployment mode: Automatically when updates are pushed via Git.
- Deployment path:
httpdocsif the repo already has the public files at its root. Set it tohttpdocs/buildor similar if your build step emits to a subdirectory.
Or from the CLI, which is faster for fleets:
plesk ext git --create-repository \
-domain example.com \
-name clientsite \
-url git@github.com:agency/clientsite.git \
-branch main \
-path httpdocs \
-auto-deploy true
Plesk creates the bare clone under /var/www/vhosts/example.com/git/clientsite/ and returns a webhook URL and a deploy key.
Step 3 — Authorise the deploy key
The first clone will fail with Permission denied (publickey) — Plesk generated a new SSH key for this subscription and your repo doesn't trust it yet. Grab the public half from the repository panel (Deploy key field), then in GitHub:
Repository settings → Deploy keys → Add deploy key, paste the key, name it plesk-example.com, and leave Allow write access unticked. The key only needs read.
In GitLab the path is Settings → Repository → Deploy keys; in Bitbucket it's Repository settings → Access keys. Same shape — read-only key per repo, scoped to the one Plesk subscription that holds it.
Back in Plesk, click Pull updates in the repository panel. The first sync runs the full clone (slow for large repos) and you should land on the latest commit on main.
Step 4 — Configure the webhook
Open the repository in Plesk, copy the Webhook URL (form: https://panel.example.net:8443/modules/git/public/web-hook.php?token=<long-hash>), and paste it into your Git provider:
- GitHub: Repo settings → Webhooks → Add webhook. Payload URL = the webhook URL. Content type =
application/json. Trigger = Just the push event. SSL verification = enabled. Plesk uses a Let's Encrypt cert by default; if you're on the self-signed admin cert, enable SSL verification anyway and front the panel with a real cert via Plesk Let's Encrypt. - GitLab: Settings → Webhooks → URL = webhook URL, Trigger = Push events. Leave Secret token blank — the auth comes from the
token=query string Plesk generated. - Bitbucket: Repository settings → Webhooks → Add webhook. Trigger on Repository push.
Test the webhook from the provider's UI — a 200 response from Plesk means the push triggered a pull and (if configured) deploy action. A 403 means the token expired or the URL is wrong. A 502 means the Plesk panel was unreachable or the webhook endpoint is firewalled.
Step 5 — Add deploy actions for build steps
Static sites work end-to-end after Step 4. PHP/Composer, Node/npm, and frontend toolchains need a build step. In the repository panel click Enable additional deployment actions and add commands one per line:
composer install --no-dev --optimize-autoloader
npm ci --omit=dev
npm run build
php artisan migrate --force
php artisan config:cache
These run as the subscription's system user from the repository root (/var/www/vhosts/example.com/git/clientsite/), not from httpdocs. The cwd matters: if your build emits to dist/, your deployment path in Step 2 should be dist/, and Plesk will sync the built directory into the document root.
Step 6 — Verify with a test commit
From a developer machine:
echo "<?php phpinfo();" > httpdocs/version.php
git add httpdocs/version.php && git commit -m "deploy test"
git push origin main
Within ~5 seconds, https://example.com/version.php should serve the new file. If it doesn't, in Plesk open Git → Repository → Pull updates to trigger manually — the output panel shows the git transcript and any deploy-action stderr.
Delete version.php once you've confirmed; serving phpinfo() from production leaks PHP version, loaded modules, and environment variables.
Common mistakes
-
Pointing deployment path at the repo root. A Laravel project at
httpdocsexposes.env,storage/,vendor/, andnode_modules/to anyone who guesses the URL. Always set the deployment path to the public subdirectory (public/,dist/,build/,httpdocs/) and let everything else stay outside the document root. -
Using HTTPS clone URLs with a personal access token. A PAT in the repo URL works until the developer rotates it or leaves. Plesk stores the URL in cleartext in the panel database, so the token is visible to anyone with panel access. SSH deploy keys scoped to the single repo are the right primitive.
-
Running database migrations from deploy actions on a shared schema. Two pushes in quick succession can race the migration table and leave the schema half-applied. Add
php artisan migrate --force --isolated(Laravel 9+) or run migrations from a manual deploy on production-impact pushes only. -
Forgetting the system user shell. Plesk silently runs the action steps with no output if the subscription user is set to
/bin/falseor/bin/rssh. Set it to/bin/bashunder Web Hosting Access for any subscription with deploy actions. For shared resellers, scope this carefully — bash access lets the client run anything as their own user.
Logs to keep open during setup
# Plesk's Git extension logs (webhook hits, pulls, deploy actions)
tail -F /usr/local/psa/var/log/git.log
# Apache/nginx access log on the panel — webhook deliveries land here
tail -F /var/log/plesk/panel_access_ssl_log
# The subscription's deploy action stderr ends up here too
journalctl -u psa -f
The webhook hits the panel as a POST to /modules/git/public/web-hook.php from your Git provider's IP. If you've front-loaded the panel with Cloudflare or another WAF, whitelist GitHub's webhook IP ranges — Cloudflare's bot-fight mode will eat the POST silently and leave the deployment in a broken state.
Does Plesk Git deployment require Web Pro or higher?+
Can I deploy from a private GitHub repo without storing a personal access token?+
Why does GitHub show my webhook delivery as failed when the deploy worked?+
How do I deploy a staging branch to a different subdomain?+
Can deploy actions read .env files outside the document root?+
What happens to uncommitted local changes on the server during a pull?+
Next steps
- If you're running ModSecurity on the panel host, whitelist the webhook endpoint — Atomic's bot rules occasionally block GitHub's POST as a scripted request.
- Pick the right edition before rolling Git deployment out across a customer base — the Web Pro vs Web Host breakdown covers the 30-domain cap that catches most resellers off guard.
- Activate or renew a Plesk license to unlock the Developer Pack and Git extension on production servers.