{"id":50432886,"url":"https://github.com/rafaelkallis/bearer-proxy","last_synced_at":"2026-05-31T15:01:59.069Z","repository":{"id":358930248,"uuid":"1243629154","full_name":"rafaelkallis/bearer-proxy","owner":"rafaelkallis","description":"A minimal nginx reverse proxy that authenticates requests using bearer tokens.","archived":false,"fork":false,"pushed_at":"2026-05-19T16:59:07.000Z","size":26,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-19T19:55:28.711Z","etag":null,"topics":["bearer","nginx","reverse-proxy"],"latest_commit_sha":null,"homepage":"","language":"Shell","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/rafaelkallis.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-05-19T14:12:35.000Z","updated_at":"2026-05-19T16:59:12.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/rafaelkallis/bearer-proxy","commit_stats":null,"previous_names":["rafaelkallis/bearer-proxy"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/rafaelkallis/bearer-proxy","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rafaelkallis%2Fbearer-proxy","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rafaelkallis%2Fbearer-proxy/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rafaelkallis%2Fbearer-proxy/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rafaelkallis%2Fbearer-proxy/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/rafaelkallis","download_url":"https://codeload.github.com/rafaelkallis/bearer-proxy/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rafaelkallis%2Fbearer-proxy/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33735663,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-05-31T02:00:06.040Z","response_time":95,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["bearer","nginx","reverse-proxy"],"created_at":"2026-05-31T15:01:57.993Z","updated_at":"2026-05-31T15:01:59.064Z","avatar_url":"https://github.com/rafaelkallis.png","language":"Shell","funding_links":[],"categories":[],"sub_categories":[],"readme":"# bearer-proxy\n\nA minimal nginx reverse proxy that authenticates requests using bearer tokens.\n\n## API Keys\n\nOne key per line. Lines starting with `#` and blank lines are ignored. Both LF and CRLF line endings are supported.\n\n```\n# api_keys.txt\nsk-alice\nsk-bob\n```\n\nKeys are loaded once at startup. **Rotating keys requires restarting the container** — there is no hot-reload.\n\n## Streaming\n\nStreaming responses (SSE, chunked transfer) are fully supported. Response buffering is disabled and connections use HTTP/1.1 keep-alive. Use `PROXY_READ_TIMEOUT` and `PROXY_SEND_TIMEOUT` to tune for long-running or slow-streaming upstreams.\n\n## Environment Variables\n\n| Variable | Required | Default | Description |\n|---|---|---|---|\n| `UPSTREAM` | ✅ | — | Backend URL to proxy to |\n| `PORT` | ❌ | `80` | Port to listen on |\n| `PROXY_READ_TIMEOUT` | ❌ | `300s` | Timeout between reads from upstream |\n| `PROXY_SEND_TIMEOUT` | ❌ | `60s` | Timeout for sending requests to upstream |\n| `CLIENT_MAX_BODY_SIZE` | ❌ | `128m` | Max request body size |\n| `API_KEYS_FILE` | ❌ | `/run/secrets/api_keys` | Path to the file containing API keys |\n\n## Deployment Scenarios\n\n### 1. Cloudflare Tunnel\n\nThe recommended public deployment. Cloudflare terminates TLS and handles per-client rate limiting at the edge. The proxy receives traffic from the local `cloudflared` daemon.\n\n```yaml\nservices:\n  cloudflared:\n    image: cloudflare/cloudflared:latest\n    command: tunnel --no-autoupdate run\n    environment:\n      - TUNNEL_TOKEN=${TUNNEL_TOKEN}\n\n  proxy:\n    image: ghcr.io/rafaelkallis/bearer-proxy:latest\n    secrets:\n      - api_keys\n    environment:\n      - UPSTREAM=http://your-backend:8080\n\n  your-backend:\n    image: your-backend\n\nsecrets:\n  api_keys:\n    file: ./api_keys.txt\n```\n\n\u003e `X-Forwarded-Proto: https` is passed through from Cloudflare. `CF-Connecting-IP` (the real client IP) is forwarded to the upstream unchanged. `X-Real-IP` will reflect the `cloudflared` daemon's IP, not the real client — use `CF-Connecting-IP` upstream if you need the real IP.\n\n### 2. Host-exposed (HTTP)\n\nDirect port mapping to the host. Suitable for internal networks or when a separate TLS terminator (Traefik, Caddy) sits in front on the same host.\n\n```yaml\nservices:\n  proxy:\n    image: ghcr.io/rafaelkallis/bearer-proxy:latest\n    ports:\n      - \"8000:80\"\n    secrets:\n      - api_keys\n    environment:\n      - UPSTREAM=http://your-backend:8080\n\n  your-backend:\n    image: your-backend\n\nsecrets:\n  api_keys:\n    file: ./api_keys.txt\n```\n\n\u003e `X-Forwarded-Proto` is omitted when there is no upstream proxy (nginx drops empty headers). `X-Real-IP` and `X-Forwarded-For` reflect the real client IP.\n\n### 3. Service-to-service (Docker network)\n\nThe proxy sits inside a Docker network, consumed by another service. No port is exposed to the host.\n\n```yaml\nservices:\n  proxy:\n    image: ghcr.io/rafaelkallis/bearer-proxy:latest\n    secrets:\n      - api_keys\n    environment:\n      - UPSTREAM=http://your-backend:8080\n\n  your-backend:\n    image: your-backend\n\n  your-client:\n    image: your-client\n    environment:\n      - API_BASE_URL=http://proxy\n      - API_KEY=sk-your-client\n\nsecrets:\n  api_keys:\n    file: ./api_keys.txt\n```\n\n\u003e All traffic stays on the Docker network. `X-Forwarded-For` and `X-Real-IP` reflect the calling service's Docker network IP.\n\n## Rate Limiting\n\nThis proxy does **not** implement rate limiting. The reasons depend on the deployment scenario:\n\n- **Cloudflare Tunnel** (recommended): all traffic arrives from the local `cloudflared` daemon, so `$remote_addr` is always the daemon's IP — not the real client's. A per-IP rate limit would bucket every real client together, making it either useless or a self-inflicted denial of service. Cloudflare's edge rate limiting operates on the real client IP and is the right place to enforce this.\n- **Service-to-service (Docker network)**: the calling service is a known, trusted peer. Rate limiting a trusted internal service adds operational friction with little security benefit.\n- **Host-exposed (HTTP)**: if you expose the proxy directly to untrusted clients, consider putting a proper reverse proxy (Traefik, Caddy, Cloudflare) in front and applying rate limits there.\n\n## Security\n\nThis proxy does **not** terminate TLS. Bearer tokens will be transmitted in plaintext unless this container runs behind a TLS terminator (e.g. Traefik, Caddy, or a cloud load balancer). Do not expose it directly on the internet without TLS.\n\nThe `Authorization` header is **stripped** before the request is forwarded to the upstream. The upstream never sees the bearer token. If your upstream requires its own authentication, set it via a separate header or embed credentials in the `UPSTREAM` URL.","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frafaelkallis%2Fbearer-proxy","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frafaelkallis%2Fbearer-proxy","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frafaelkallis%2Fbearer-proxy/lists"}