osec Edge docs: every connect mode, step by step

Get started

  1. Sign in (email code, Google or Apple). The console opens.
  2. Console → Edge sites → Add site: a name, its hostnames (www.example.com, example.com), nearest edge (US or EU) and connect mode.
  3. Copy the secret (oss_...) shown once. The public key (osk_...) stays under Keys.
  4. Open Connect under the site: the snippet for its mode and region.
  5. 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

  1. 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.
  2. 204: serve the page. 403: blocked. 401: send the browser to /.osec/challenge on your own host.
  3. The challenge page is the edge's, served through /.osec/* on your host, so the pass cookie lands on your domain.
  4. Solved: the osec_pass cookie lets the visitor through for 30 minutes.
  5. 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).

  1. Check the module: nginx -V 2>&1 | grep auth_request.
  2. Download the three files (US sites: osec-us.conf instead of osec-eu.conf).
  3. In each server {} of the site: include snippets/osec-eu.conf;
  4. 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.conf

nginx

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.

  1. Download osec-eu.caddy (or osec-us.caddy) next to the Caddyfile.
  2. At the top of the Caddyfile, after the global options: import osec-eu.caddy
  3. In each site block: import osec
  4. caddy reload.

bash

curl -fsS -O https://osec.one/assets/osec-edge/caddy/osec-eu.caddy

caddyfile

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.

  1. Download osec-eu.yml (or osec-us.yml) into the dynamic config folder (providers.file.directory).
  2. Add the middleware osec@file to every router of the site.
  3. 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.yml

yaml: 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.

  1. a2enmod rewrite proxy_http proxy_balancer lbmethod_byrequests ssl headers
  2. Download osec-check.py and osec-site.conf to /etc/apache2/osec/; chmod 755 osec-check.py.
  3. In each <VirtualHost> of the site: Include /etc/apache2/osec/osec-site.conf
  4. US first: swap the two edge names in the RewriteMap line and the two BalancerMember lines.
  5. 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.

  1. npm install the package from osec.one.
  2. app.use(osec({ region: 'eu' })) before your routes ('us' for US sites).
  3. 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.tgz

js

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.

  1. pip install the package from osec.one.
  2. app.add_middleware(OsecMiddleware, region="eu") ("us" for US sites).
  3. Behind your own proxy: ip_header="x-forwarded-for".

bash

pip install https://osec.one/assets/osec-edge/osec_edge-0.1.0.tar.gz

python

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

  1. In osec.one: add the site with WordPress switched on. Copy the secret.
  2. Download the plugin zip. WordPress → Plugins → Add New → Upload Plugin → activate.
  3. Settings → osec.one edge: nearest edge (eu/us), site key osk_..., secret oss_... (or define('OSEC_EDGE_SECRET', 'oss_...'); in wp-config.php).
  4. Forms are checked at once: login, registration, lost password, guest comments, Contact Form 7.
  5. 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.zip

php: 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.

  1. Put the widget inside the form and load the script from your nearest edge.
  2. On submit your server gets the field osec-token.
  3. POST it with your secret to /v1/verify. Accept the form only if success is true.
  4. 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.

  1. Add the site in mode Log agent. Copy the secret.
  2. On each server (root, python3 3.8+ and systemd): download the four files.
  3. sh install.sh oss_YOUR_SECRET eu (or us).
  4. 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 eu

bash: 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.

  1. Add the site in mode Full proxy (region EU) with your origin, e.g. https://origin.example.com or https://203.0.113.10.
  2. 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.
  3. Within minutes of DNS pointing at us, each host gets its own Let's Encrypt certificate. HTTP then redirects to HTTPS.
  4. 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

  1. On by default for static files (images, CSS, JS, fonts...): GET and HEAD only.
  2. Origin sends Cache-Control or Expires: that wins. No cache headers: kept 4 hours.
  3. Pages (HTML) are cached only in standard mode, and only when the origin's headers allow it.
  4. 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)

  1. Caps apply before the edge check, to every request in proxy mode.
  2. 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

  1. POST your secret and up to 30 URLs to /v1/purge on the EU edge, or everything: true.
  2. URLs must be on the site's hosts (http or https). They're gone within seconds.
  3. 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

  1. 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.