osec Edge docs: every connect mode, step by step
Get started
- Sign in (email code, Google or Apple). The console opens.
- Console → Edge sites → Add site: a name, its hostnames (www.example.com, example.com), nearest edge (US or EU) and connect mode.
- Copy the secret (oss_...) shown once. The public key (osk_...) stays under Keys.
- Open Connect under the site: the snippet for its mode and region.
- Changes reach both edges in about 20 seconds.
- Edges: US https://edge-us.osec.one (New York), EU https://edge-eu.osec.one (Europe).
- A hostname can be on one site only. Hosts that aren't on any site always pass.
- Try a new site with Log only on: nothing is blocked, the dashboard shows what would have been.
- WordPress site: switch WordPress on, or wp-login.php and xmlrpc.php are blocked as bad paths.
How a check works
- Your server (or plugin) asks the edge GET /v1/auth with the visitor's IP, host, path, method, user agent and the osec_pass cookie.
- 204: serve the page. 403: blocked. 401: send the browser to /.osec/challenge on your own host.
- The challenge page is the edge's, served through /.osec/* on your host, so the pass cookie lands on your domain.
- Solved: the osec_pass cookie lets the visitor through for 30 minutes.
- Nearest edge silent for 300 ms or failing: the other edge. Both down: the request goes through (fail open).
- Only the osec_pass cookie is sent, never your site's cookies or Authorization header. No request bodies.
- Order on the edge: shared bans, custom rules, bad paths, WAF, rate limit.
- Good bots (by user agent) are never challenged or limited.
nginx (auth_request)
Needs ngx_http_auth_request_module (in the Ubuntu, Debian and nginx.org packages).
- Check the module: nginx -V 2>&1 | grep auth_request.
- Download the three files (US sites: osec-us.conf instead of osec-eu.conf).
- In each server {} of the site: include snippets/osec-eu.conf;
- nginx -t && systemctl reload nginx.
bash
# 1. both edges (http context)
curl -fsS -o /etc/nginx/conf.d/osec-upstreams.conf https://osec.one/assets/osec-edge/nginx/osec-upstreams.conf
# 2. the check: nearest edge first (osec-eu.conf or osec-us.conf)
curl -fsS -o /etc/nginx/snippets/osec-eu.conf https://osec.one/assets/osec-edge/nginx/osec-eu.conf
curl -fsS -o /etc/nginx/snippets/osec-check-headers.conf https://osec.one/assets/osec-edge/nginx/osec-check-headers.confnginx
server {
server_name www.example.com;
include snippets/osec-eu.conf; # osec Edge check
location / { try_files $uri $uri/ /index.php?$args; }
}- A location with its own error_page lines needs error_page 401 = @osec_challenge; too.
- auth_request off; in a location skips the check (health checks, webhooks, basic-auth areas).
- A location that only does return 200 answers before the check: files, try_files, proxy_pass and fastcgi_pass are checked.
- /.osec/ belongs to the snippet.
- Worst case added time: 300 ms + 300 ms, then fail open.
Caddy (forward_auth)
Caddy 2.7 or newer.
- Download osec-eu.caddy (or osec-us.caddy) next to the Caddyfile.
- At the top of the Caddyfile, after the global options: import osec-eu.caddy
- In each site block: import osec
- caddy reload.
bash
curl -fsS -O https://osec.one/assets/osec-edge/caddy/osec-eu.caddycaddyfile
import osec-eu.caddy
www.example.com {
import osec
reverse_proxy 127.0.0.1:8080
}- The file runs a local relay on 127.0.0.1:8139 that tries both edges and answers 204 when both are down (fail open). Port taken: change it in the three places in the file.
- Behind a load balancer or CDN: set trusted_proxies so the visitor's IP is checked, not the balancer's.
- Sites that wrap everything in route { }: import osec before the route.
Traefik (forwardAuth)
Traefik v2.10+ or v3 with the file provider.
- Download osec-eu.yml (or osec-us.yml) into the dynamic config folder (providers.file.directory).
- Add the middleware osec@file to every router of the site.
- It also adds routers that send /.osec/* on any host to the edge, with failover by health check.
bash
curl -fsS -o /etc/traefik/dynamic/osec-eu.yml https://osec.one/assets/osec-edge/traefik/osec-eu.ymlyaml: docker labels
labels:
- traefik.http.routers.web.middlewares=osec@file- Traefik has no fallback for forwardAuth: if the edge can't be reached, Traefik answers 500 (not fail open). Need fail open: put Caddy or nginx in front, or use a middleware.
- No 300 ms limit (forwardAuth has no timeout setting).
- Behind a load balancer or CDN: set the entry point's forwardedHeaders.trustedIPs and trustForwardHeader: true.
Apache (mod_rewrite + helper)
Apache 2.4 with python3 and the modules rewrite, proxy_http, proxy_balancer, lbmethod_byrequests, ssl, headers.
- a2enmod rewrite proxy_http proxy_balancer lbmethod_byrequests ssl headers
- Download osec-check.py and osec-site.conf to /etc/apache2/osec/; chmod 755 osec-check.py.
- In each <VirtualHost> of the site: Include /etc/apache2/osec/osec-site.conf
- US first: swap the two edge names in the RewriteMap line and the two BalancerMember lines.
- apachectl configtest && systemctl reload apache2.
bash
mkdir -p /etc/apache2/osec && cd /etc/apache2/osec
curl -fsS -O https://osec.one/assets/osec-edge/apache/osec-check.py -O https://osec.one/assets/osec-edge/apache/osec-site.conf
chmod 755 osec-check.py- The helper keeps a connection open: about 3 ms a check, one at a time. Fine for small and mid sites; busy sites: nginx or Caddy in front.
- Only IP, host, URI, method, user agent and osec_pass go to the edge.
- RHEL: SSLProxyCACertificateFile /etc/pki/tls/certs/ca-bundle.crt.
- Behind a load balancer or CDN: mod_remoteip so REMOTE_ADDR is the visitor.
Express / Node (middleware)
Node 18+, no dependencies.
- npm install the package from osec.one.
- app.use(osec({ region: 'eu' })) before your routes ('us' for US sites).
- Behind your own proxy: app.set('trust proxy', 1) so req.ip is the visitor.
bash
npm install https://osec.one/assets/osec-edge/osec-edge-express-0.1.0.tgzjs
const express = require("express");
const osec = require("osec-edge-express");
const app = express();
app.set("trust proxy", 1); // only behind your own proxy
app.use(osec({ region: "eu", skip: (req) => req.path === "/health" }));
app.get("/", (req, res) => res.send("hello " + req.osec.action));- req.osec = { action, reason }; reason edge_down when it failed open.
- Options: key (osk_...), host, skip(req), getIp(req), timeout / backupTimeout (ms), coldTimeout (1500 ms), warm (true), edges.
- Only the osec_pass cookie goes to the edge.
FastAPI / Starlette (ASGI middleware)
Python 3.9+, needs httpx.
- pip install the package from osec.one.
- app.add_middleware(OsecMiddleware, region="eu") ("us" for US sites).
- Behind your own proxy: ip_header="x-forwarded-for".
bash
pip install https://osec.one/assets/osec-edge/osec_edge-0.1.0.tar.gzpython
from fastapi import FastAPI, Request
from osec_edge import OsecMiddleware
app = FastAPI()
app.add_middleware(OsecMiddleware, region="eu")
@app.get("/")
def home(request: Request):
return {"osec": request.state.osec} # {"action": "pass", "reason": ...}- Works with any ASGI app. Websockets aren't checked.
- Options: key, host, ip_header, skip(scope), timeout / backup_timeout (s), cold_timeout (1.5 s), warm (True), edges, client.
- Only the osec_pass cookie goes to the edge.
WordPress plugin
- In osec.one: add the site with WordPress switched on. Copy the secret.
- Download the plugin zip. WordPress → Plugins → Add New → Upload Plugin → activate.
- Settings → osec.one edge: nearest edge (eu/us), site key osk_..., secret oss_... (or define('OSEC_EDGE_SECRET', 'oss_...'); in wp-config.php).
- Forms are checked at once: login, registration, lost password, guest comments, Contact Form 7.
- Other forms: [osec_check] inside the form, then osec_edge_verify() when it's posted.
download
https://osec.one/assets/osec-edge/osec-edge-wordpress.zipphp: your own form
<form method="post">
<!-- fields -->
<?php echo do_shortcode('[osec_check]'); ?>
<button>Send</button>
</form>
<?php
if ($_SERVER['REQUEST_METHOD'] === 'POST' && !osec_edge_verify()) {
wp_die('Please try again.');
}- Both edges down: forms go through (filter osec_edge_fail_open to change that).
- Every-request check: off by default. On nginx, Caddy or Apache the server connectors are faster.
- nginx that denies dotfiles: add location ^~ /.osec/ { try_files $uri /index.php?$args; } before that rule.
- Behind your own proxy: define('OSEC_EDGE_IP_HEADER', 'HTTP_X_FORWARDED_FOR');
Human check on forms (widget + verify)
For any stack, without checking every request.
- Put the widget inside the form and load the script from your nearest edge.
- On submit your server gets the field osec-token.
- POST it with your secret to /v1/verify. Accept the form only if success is true.
- Check hostname in the answer is yours.
html
<form method="post" action="/contact">
<input name="email">
<div class="osec-check" data-sitekey="osk_YOUR_SITE_KEY"></div>
<button>Send</button>
</form>
<script src="https://edge-eu.osec.one/v1/osec-check.js" async defer></script>bash: verify on your server
curl -X POST https://edge-eu.osec.one/v1/verify -H 'Content-Type: application/json' \
-d '{"secret": "oss_YOUR_SECRET", "token": "<osec-token>"}'
# {"success": true, "hostname": "www.example.com"}- Each token works once and expires after 5 minutes.
- Timeout on one edge: verify on the other.
- The widget only runs on the site's hosts (and localhost while you build).
Log agent (shared ban list for your servers)
A small Linux service: reads nginx and ssh logs, bans attackers in an nginx deny file, shares bans with every server of the site and both edges. Adds no time to requests.
- Add the site in mode Log agent. Copy the secret.
- On each server (root, python3 3.8+ and systemd): download the four files.
- sh install.sh oss_YOUR_SECRET eu (or us).
- osec-agent check: logs readable, edge answers.
bash
mkdir osec-agent && cd osec-agent
for f in osec_agent.py install.sh osec-agent.service osec-agent.conf.example; do
curl -fsSO https://osec.one/assets/osec-agent/$f
done
sh install.sh oss_YOUR_SECRET eubash: day to day
osec-agent status # live bans: local and shared, minutes left, reason
osec-agent unban 1.2.3.4 # lift a ban here and on the edges
osec-agent check # logs readable, edge answers
journalctl -u osec-agent # what it did- Bans: 5 bad-path probes in 10 min, 60 4xx answers in 10 min (401 not counted), 6 ssh failures in 10 min → 60 minutes.
- Deny file: /etc/nginx/conf.d/osec-deny.conf, written only on change, nginx -t before reload, old file back when the test fails.
- Firewall too: on_ban = ufw insert 1 deny from {ip} and on_unban in /etc/osec-agent.conf.
- Never banned: loopback, private ranges, your allow list, search engine and AI crawler user agents.
- Both edges down: it keeps banning locally and sends the backlog later.
- Settings: /etc/osec-agent.conf (mode 600, holds the secret). Memory capped at 200 MB.
Full proxy (DNS points at us)
Traffic goes through the EU edge to your origin: every check above plus TLS, cache, caps and purge.
- Add the site in mode Full proxy (region EU) with your origin, e.g. https://origin.example.com or https://203.0.113.10.
- DNS: www CNAME edge-eu.osec.one. Apex: an A record to the address of edge-eu.osec.one (dig +short edge-eu.osec.one), and remove other A/AAAA records for that name.
- Within minutes of DNS pointing at us, each host gets its own Let's Encrypt certificate. HTTP then redirects to HTTPS.
- Check: curl -sI https://www.example.com shows X-Osec-Cache.
dns
www.example.com. CNAME edge-eu.osec.one.
example.com. A <dig +short edge-eu.osec.one>
# no other A or AAAA records for these names- Every A/AAAA answer for the host must point at the edge before a certificate is ordered. Not yet: we look again every 5 minutes.
- Your origin gets X-Forwarded-For and X-Real-IP (the visitor), X-Forwarded-Host and X-Forwarded-Proto. Allow only the edge at your origin's firewall if you can.
- The Host header is the visitor's host. Origin that needs its own name: ask support to set the origin host header.
- Origin certificate isn't checked yet (like 'Full' at other providers); a self-signed origin works.
- WebSockets and bodies up to 25 MB pass through.
- /.well-known/acme-challenge/ falls through to your origin, so it can still renew its own certificate.
Proxy cache
- On by default for static files (images, CSS, JS, fonts...): GET and HEAD only.
- Origin sends Cache-Control or Expires: that wins. No cache headers: kept 4 hours.
- Pages (HTML) are cached only in standard mode, and only when the origin's headers allow it.
- See it: the X-Osec-Cache header is HIT, MISS or BYPASS.
- Modes: static (default), standard (pages too, origin's headers decide), off.
- Never cached: Cache-Control private, no-store or no-cache, answers with Set-Cookie, requests with Authorization.
- Origin down: the last cached copy is served.
- 404s are kept 60 seconds.
- The edge check still runs on every request, cache hits too.
- Mode and the no-headers time (60 s to 30 days) are per site; console switches are coming, until then support sets them.
Proxy caps (per site)
- Caps apply before the edge check, to every request in proxy mode.
- Over a request cap: 429. Body over the size cap: 413.
defaults
rps 100 requests a second for the whole site (1-2000)
ip_rps 20 requests a second per visitor IP (1-500)
ip_conns 30 open connections per visitor IP (1-500)
body_mb 25 largest request body in MB (1-100)
# bursts up to 2x the rate are let through at once- These are on top of the site's rate limit (which challenges or blocks).
- Per-site values: console switches are coming, until then support sets them.
Purge the proxy cache
- POST your secret and up to 30 URLs to /v1/purge on the EU edge, or everything: true.
- URLs must be on the site's hosts (http or https). They're gone within seconds.
- everything: the whole site's cache is dropped at once.
bash: some URLs
curl -X POST https://edge-eu.osec.one/v1/purge -H 'Content-Type: application/json' \
-d '{"secret": "oss_YOUR_SECRET", "urls": ["https://www.example.com/app.css", "https://www.example.com/logo.png"]}'bash: everything
curl -X POST https://edge-eu.osec.one/v1/purge -H 'Content-Type: application/json' \
-d '{"secret": "oss_YOUR_SECRET", "everything": true}'- 10 calls per site a minute (429 above).
- 403: wrong secret. 400 no-proxy-here: the site isn't in proxy mode on this edge (the US edge has no proxy cache yet).
- A URL cached in several variants (Vary) is purged with everything: true.
Site settings
- Console → Edge sites → your site → settings, then Save. Both edges have it in about 20 seconds.
settings
Log only on/off nothing blocked, the dashboard shows what would have been
WordPress on/off wp-login.php and xmlrpc.php allowed
Bad paths on/off block .env, .git/, dumps, phpinfo... (default on)
WAF block/log/off SQLi, XSS, traversal, command injection, scanners, bad UAs
Rate limit n per s per IP, then challenge or block (default 300 per 60 s, challenge; 0 = off)
Auto-ban on/off repeated blocks from one IP → shared ban on both edges- Keys: up to 5 live per site; revoking one ends the passes and tokens made with it.
- Traffic: the Traffic button per site shows passed, challenged, solved and blocked for 1 hour to 30 days, per edge, and banned IPs.