A tiny proxy to make browser-only calendar links (for example Office 365 reachcalendar.ics) usable by calendar clients that can't authenticate or set custom User-Agent headers.
Run a single container (secure defaults):
docker run -d --restart=unless-stopped \
--name calendar-proxy \
-p 8000:8000 \
--read-only \
--cap-drop=ALL \
--security-opt=no-new-privileges:true \
--memory=512m \
--cpus=0.5 \
--tmpfs=/tmp:noexec,nosuid,size=100m \
--tmpfs=/var/tmp:noexec,nosuid,size=50m \
--env-file .env \
calendar-proxy.env (example, keep this file private):
PROXY_TOKENS=longtoken123
ALLOWED_HOSTS=example.com,calendar.example.org
LOG_LEVEL=WARNING
DISABLE_ACCESS_LOG=true
Notes:
- Prefer
--env-fileover inline-eto avoid leaking secrets in process lists or shell history. - Container runs as non-root user (UID 1000) for enhanced security.
- Hardened with
--read-only, capability drops,no-new-privileges, resource limits, and temporary filesystems. - Use
REDIS_URLwhen running multiple containers for shared rate-limiting. - For production, consider using
docker-compose.production.ymlwhich includes additional security measures.
PROXY_TOKENS(required): comma-separated secret tokens used in subscription URLs.ALLOWED_HOSTS(recommended): comma-separated allowed upstream hostnames.REDIS_URL(optional):redis://...for cross-process rate limiting.RATE_LIMIT_PER_MIN(default60): per-token request limit per minute.UPSTREAM_USER_AGENT(default: Safari on macOS): User-Agent header sent to upstream servers.MAX_RESPONSE_BYTES(default:5242880bytes / 5MB): maximum response size to prevent memory exhaustion.CONNECT_TIMEOUT(default:10seconds): timeout for establishing upstream connections.READ_TIMEOUT(default:20seconds): timeout for reading upstream response data.ALLOWED_CONTENT_TYPES(default:text/calendar,text/plain,application/octet-stream): comma-separated allowed upstream content types.LOG_LEVEL(default:INFO): application log level (DEBUG, INFO, WARNING, ERROR).DISABLE_ACCESS_LOG(default:true): set tofalseto enable logging of URLs and accesses.
docker build -t calendar-proxy .For production use, a hardened docker-compose.production.yml is provided:
# Copy and customize the production compose file
cp docker-compose.production.yml docker-compose.yml
# Create secure environment file
echo "PROXY_TOKENS=$(openssl rand -hex 40)" > .env
# Deploy with enhanced security
docker-compose up -dThe production configuration includes:
- Non-root execution: Runs as UID/GID 1000
- Read-only filesystem: Prevents runtime modifications
- Capability restrictions: Drops all capabilities except essential ones
- Resource limits: Memory (512MB) and CPU (0.5 cores) constraints
- No new privileges: Prevents privilege escalation
- Temporary filesystems: Secure
/tmpand/var/tmpmounts - Enhanced logging: Production-ready log levels
- Endpoint:
GET /sub/{token}/{b64url}/{name}.icstoken: secret in the path.b64url: URL-safe base64 (no padding) of the full upstream URL.name.ics: final filename for clients.- Optional
uaquery param: override User-Agent used to fetch upstream.
Security & runtime behaviour:
- Container security: Runs as non-root user, read-only filesystem, dropped capabilities, resource limits.
- Validates
tokenagainstPROXY_TOKENS. - Resolves
b64urlhostname and refuses private/loopback/link-local/multicast/reserved IPs (SSRF protection). - Enforces
ALLOWED_HOSTSwhen set. - Validates upstream content types against
ALLOWED_CONTENT_TYPES. - Per-token rate limiting (Redis if
REDIS_URLset, otherwise in-process fallback with automatic cleanup). - Streams upstream response, enforces timeouts and a maximum response byte cap.
- Strips client cookies and Authorization; forwards only safe upstream headers (e.g.
Content-Type,Content-Disposition). - Sanitizes URLs in logs to prevent exposure of sensitive information (when
DISABLE_ACCESS_LOG=true).
- Office 365 shared URLs are browser-centric
- Shared
reachcalendar.icslinks are designed to be opened by a browser, which may provide cookies or other auth automatically; macOS Calendar does not.
- User-Agent restrictions
- Microsoft checks User-Agent and rejects unsupported clients. Requests from macOS
CalendarAgentoften get a "Outlook is not supported on this browser" page instead of an ICS.
- No auth flow support
- Some shared calendars require Microsoft authentication (cookies/OAuth). macOS Calendar cannot perform those interactive browser flows.
Workarounds: use this proxy (fetch with a browser-like UA), import into a service that fetches server-side, or download & import manually.
Generate a URL-safe base64 without padding (shell):
python - <<'PY'
import base64
u='https://example.com/cal.ics'
print(base64.urlsafe_b64encode(u.encode()).decode().rstrip('='))
PYOr without Python (using base64 + tr):
echo -n 'https://example.com/cal.ics' | base64 -w0 | tr '+/' '-_' | tr -d '='Subscription URL:
https://proxy.example.com/sub/<token>/<b64url>/Work.ics
Open http://<host>:8000/ in a browser to use the included index.html UI for building links.
GET /sub/{token}/{b64url}/{name}.ics— subscription proxy endpoint.GET /healthz— health check JSON response.GET /version— version information JSON response (includes version, build date, and commit).GET /— web UI for building calendar subscription URLs.GET /static/{path}— static files (CSS, etc.) for the web UI.
If you want an admin UI for token management, Redis-backed caching, or a docker-compose.yml with Redis, tell me which and I'll add it.