lfs-proxy#
Git LFS server on Cloudflare Workers + R2. No egress fees, no file size limit.
The worker never touches file data — it authenticates requests, then hands back presigned S3 URLs so clients upload/download straight to R2.
How it works#
UPLOAD (git lfs push) DOWNLOAD (git lfs pull)
Client Worker R2 Client Worker R2
│ │ │ │ │ │
│ POST batch │ │ │ POST batch │ │
│ op:"upload" │ │ │ op:"download"│ │
├─────────────►│ │ ├─────────────►│ │
│ │ HEAD obj │ │ │ HEAD obj │
│ ├───────────►│ │ ├───────────►│
│ │◄───────────┤ │ │◄───────────┤
│ │ │ │ │ │
│◄─────────────┤ │ │◄─────────────┤ │
│ presigned PUT│ │ │ presigned GET│ │
│ + verify url │ │ │ │ │
│ │ │ │ GET ────────────────────►│
│ PUT ────────────────────►│ │◄──────────────── bytes ──┤
│ x-amz-meta-sha256: oid │ │ │ │
│◄──────────────── 200 OK ─┤
│ │ │
│ POST verify │ │
│ {oid, size} │ │
├─────────────►│ │
│ │ HEAD obj │
│ │ check meta │
│ ├───────────►│
│ │◄───────────┤
│◄─────────────┤ │
│ 200 OK │ │
R2 storage layout#
lfs-storage/ ← R2 bucket
my-repo/ ← per-repo prefix
ab3f7c... (64 hex chars) ← object data
customMetadata:
sha256 = "ab3f7c..." ← integrity tag (set via x-amz-meta-sha256)
Auth#
R2 S3 API credentials double as Basic Auth credentials. The worker compares them with timingSafeEqual, then uses the same key pair to sign presigned URLs.
Git remote URL format:
https://{R2_ACCESS_KEY_ID}:{R2_SECRET_ACCESS_KEY}@{worker-host}/{repo}.git/info/lfs
Prerequisites#
- Cloudflare account with Workers and R2 enabled
- An R2 bucket (e.g.
lfs-storage) - An R2 S3 API token with read + write permissions
Deploy#
# set secrets
npx wrangler secret put R2_ACCOUNT_ID
npx wrangler secret put R2_ACCESS_KEY_ID
npx wrangler secret put R2_SECRET_ACCESS_KEY
# deploy
npx wrangler deploy
R2_BUCKET_NAME is set in wrangler.jsonc as a plain-text var.
Client config#
Add a .lfsconfig to your repo:
[lfs]
url = https://{key}:{secret}@{worker-host}/{repo}.git/info/lfs
access = basic
Development#
bun install
npx wrangler dev # local server on :8787
bun run test # vitest with Cloudflare Workers pool
API routes#
| Route | Method | Description |
|---|---|---|
/:repo[.git]/info/lfs/objects/batch |
POST | Batch API — returns presigned URLs for upload/download |
/:repo[.git]/info/lfs/objects/verify |
POST | Verify — confirms object exists with correct size + metadata |
| Any other path | * | 404 |
| Non-POST to valid route | * | 405 |
All responses use application/vnd.git-lfs+json. Failed auth returns 401 with WWW-Authenticate: Basic.