Documentation
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 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. Git owns the fallback of its
host: path backends such as CalDAV or the MCP endpoint share it, and the
setting refuses a host whose fallback a site or another backend owns
(Virtual hosts).
At process start the same values come from flags:
| CLI flag | Env var | Meaning |
|---|---|---|
--backend-git-http[=BOOL] | EVERLOCK_BACKEND_GIT_HTTP | serve git over HTTP (off by default) |
--backend-git-http-vhost <HOST> | EVERLOCK_BACKEND_GIT_HTTP_VHOST | host 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
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.
| Path | Serves |
|---|---|
GET /{repo}[.git]/info/refs?service=… | ref advertisement for that service |
POST /{repo}[.git]/git-upload-pack | fetch and clone |
POST /{repo}[.git]/git-receive-pack | push |
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:
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
reader and a write right — writer on the repository, or on <repo>:pulls
for pull requests only.
See Access control and grants.
Two denial codes, and the difference matters to a git client:
401when no credentials were offered. Git reads the challenge and retries with its credential helper, so an anonymous denial must not be a flat403.403when credentials were offered and are insufficient.
A git-receive-pack advertisement requires what a push requires 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
shallowboundary lines, the push one declaresreport-status,delete-refsandatomic. - A fetch may ask for a commit by its id —
git fetch <remote> <commit>— when a branch or tag still reaches it. A commit no ref reaches is refused withnot our ref. info/refsfor a service other thangit-upload-packandgit-receive-packis answered403.- Both POSTs take only the type git sends,
application/x-git-<service>-request, and answer anything else415, so a page elsewhere cannot have a browser post to them. Both responses carryapplication/x-git-<service>-result. - Request bodies arrive chunked, may carry
Expect: 100-continue, and are gunzipped whenContent-Encoding: gzipis set. A fetch request — the commits a client wants and has — inflates to at most 64 MiB; past that the answer is413. A push is as large as the history it brings. - Each request is a session of its own. The fetch side acknowledges the
commits a client has in common with it (
multi_ack_detailed), so an incremental fetch transfers only what is new. - A client offering
Git-Protocol: version=2is 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 unpacked in memory before any of it is written, as over
SSH; one that needs more memory than the machine has available is refused
with not enough memory to unpack this push, and the server keeps running.