Documentation

Last updated: 2026-09-27

Git over HTTP

Repositories are served over HTTPS as well as SSH, using git's standard smart HTTP protocol. Clone, fetch and push work with an unmodified git client.

One backend serves three things on one host: the transport described here, the web UI, and the runner protocol that workflows use. They come up together because a runner's instance URL is the host it clones from.

Enabling it

Turn the backend on and give it a host:

/backends enable git-http
/server settings set git.http.vhost git.example.com

git.http.vhost applies live — routing, the runner server URL, the web UI's instance name, and the vhost publication that binds HTTPS, issues a certificate and announces a .local name all move to the new host by the time the command returns. See Server settings.

At process start the same values come from flags:

CLI flagEnv varMeaning
--backend-git-http[=BOOL]EVERLOCK_BACKEND_GIT_HTTPserve git over HTTP (off by default)
--backend-git-http-vhost <HOST>EVERLOCK_BACKEND_GIT_HTTP_VHOSThost the transport answers on; the git.http.vhost setting wins

The enabled flag persists in config/git-http.toml in the system store, which is what /backends enable git-http writes. The Git engine itself has no enable flag: it runs when either transport does, so turning on HTTP also brings up the repositories, the storage root, and the git.gc and git.retention jobs.

Clone URLs

git clone https://git.example.com/my-project.git
git clone https://git.example.com/my-project

Both work. Git appends /info/refs to whatever clone URL it is handed, so the suffix is optional and each route is served with and without it.

PathServes
GET /{repo}[.git]/info/refs?service=…ref advertisement for that service
POST /{repo}[.git]/git-upload-packfetch and clone
POST /{repo}[.git]/git-receive-packpush

Because of this, info/refs, git-upload-pack and git-receive-pack are reserved second path segments under a repository name.

Credentials

Authenticate with a password or an API key — the same funnel every other Everlock HTTP surface uses. An API key works as the Basic password whatever username accompanies it, which is the convention git's credential helper and forge access tokens rely on:

git clone https://x:evapi_…@git.example.com/my-project.git

The key names its own user, so the username is ignored.

Access is checked against http/git/<repo>, or */git/<repo> for a grant that covers every transport at once. A fetch takes reader; a push takes writer. See Access control and grants.

Two denial codes, and the difference matters to a git client:

  • 401 when no credentials were offered. Git reads the challenge and retries with its credential helper, so an anonymous denial must not be a flat 403.
  • 403 when credentials were offered and are insufficient.

A git-receive-pack advertisement requires write access and answers 401 otherwise, so a client authenticates before uploading a pack rather than after. Wrong credentials feed the same per-IP failure window SMTP and IMAP use; a request carrying none is git's ordinary first probe and is not counted against it.

Protocol details

  • The advertisement carries the smart-HTTP prologue — the service name as a pkt-line, then a flush — with Cache-Control: no-cache, so no proxy hands a client a stale ref list.
  • The two services take different advertisements: the fetch one carries shallow boundary lines, the push one declares report-status.
  • Both POST responses carry application/x-git-<service>-result.
  • Request bodies arrive chunked, may carry Expect: 100-continue, and are gunzipped when Content-Encoding: gzip is set.
  • A client offering Git-Protocol: version=2 is answered with the v0 advertisement the engine speaks, and falls back on its own.

Shallow clones work here exactly as they do over SSH: the boundary handling, deepen negotiation, per-ref capability enforcement and missing-object rejection live in the protocol engine, which is transport-free. See History retention.

Load and limits

Pack generation is the one endpoint that does real work for an unauthenticated caller, so a global semaphore bounds concurrent anonymous fetches at four. Beyond it a request is answered 503 with Retry-After.

Authenticated actors are not counted against that limit, because a fleet of workflow runners cloning once per task must not be throttled against strangers.

A fetch streams: the pack generator runs on the blocking pool and hands chunks through a bounded channel, so a slow client slows the generator rather than buffering a whole pack in memory, and a client that disappears releases the thread. A push is bounded by what the client sends — the session buffers the pack in memory before unpacking, the same bound the SSH path has.

git http https transport credentials