Point your domain at PQ-Proxy. Every connection is automatically protected with X25519MLKEM768.
Dashboard: proxy.fipsign.dev · API base: https://proxy-api.fipsign.dev/api/v1
| Requirement | Details |
|---|---|
| Account | Register at proxy.fipsign.dev — no credit card required for the 7-day trial |
| A domain you control | You need to be able to update the DNS A record for the domain you want to protect |
| A backend | Any HTTP or HTTPS server — cloud, on-premise, or serverless. Must be reachable from the internet. |
| API key | Available in the dashboard under Settings. Format: pqp_live_... |
| Field | Value | Notes |
|---|---|---|
| Public domain | api.yourcompany.com | The domain your clients connect to. Must have an A record pointing to PQ-Proxy. |
| Backend host | backend.yourcompany.com | Where PQ-Proxy forwards traffic after terminating TLS. Must be a public hostname or IP — private ranges (10.x, 192.168.x, 172.16–31.x), loopback (localhost, 127.x), and cloud metadata addresses (169.254.x) are not allowed. |
| Backend port | 443 | Use 443 for HTTPS backends, 80 for HTTP. Default: 443. |
| Backend TLS | ✓ enabled | Enable if your backend serves HTTPS. Disable for plain HTTP backends. Required for HTTP/2 and gRPC backends — when enabled, PQ-Proxy negotiates HTTP/2 via ALPN automatically. |
# In your DNS provider (Cloudflare, Route53, etc.) Type: A Name: api # or @ for the root domain Value: 137.66.56.190 TTL: 3600
# Check that the A record resolves correctly dig api.yourcompany.com A +short # Should return: 137.66.56.190
curl -sv https://api.yourcompany.com 2>&1 | grep -i "ssl\|tls\|handshake\|x25519"
In the dashboard, go to Health — your domain should show ● healthy once the first connection is made.
Go to Dashboard → Settings. Your API key starts with pqp_live_.
# Pass the API key in every request curl -s https://proxy-api.fipsign.dev/api/v1/tenant \ -H "X-Api-Key: pqp_live_YOUR_KEY_HERE"
curl -s https://proxy-api.fipsign.dev/api/v1/tenant \
-H "X-Api-Key: pqp_live_..."
curl -s https://proxy-api.fipsign.dev/api/v1/tenant/domains \
-H "X-Api-Key: pqp_live_..."
id from /tenant/domains.curl -s https://proxy-api.fipsign.dev/api/v1/tenant/domains/DOMAIN_ID/certificate \ -H "X-Api-Key: pqp_live_..."
acme means the certificate was issued automatically by Let's Encrypt. byoc means you uploaded your own certificate.curl -s https://proxy-api.fipsign.dev/api/v1/tenant/billing \
-H "X-Api-Key: pqp_live_..."
low_balance_warning to trigger wallet top-ups before service is interrupted. The wallet is charged $0.04/hour per active domain.?page=1&per_page=50 (default: 50, max: 200).curl -s https://proxy-api.fipsign.dev/api/v1/tenant/billing/transactions \
-H "X-Api-Key: pqp_live_..."
curl -s https://proxy-api.fipsign.dev/api/v1/tenant/metrics \
-H "X-Api-Key: pqp_live_..."
curl -s "https://proxy-api.fipsign.dev/api/v1/tenant/connections?page=1&per_page=20" \ -H "X-Api-Key: pqp_live_..."
curl -s https://proxy-api.fipsign.dev/api/v1/tenant/connections/export \
-H "X-Api-Key: pqp_live_..." \
-o connections.csv
curl -s https://proxy-api.fipsign.dev/api/v1/tenant/health \
-H "X-Api-Key: pqp_live_..."
Go to Dashboard → Domains, click Upload custom cert on your domain, and paste your PEM-encoded certificate and private key.
| Field | Format | Notes |
|---|---|---|
| Certificate | PEM | Full chain preferred — include intermediate certificates |
| Private key | PEM | RSA or ECDSA. The key must match the certificate. |
Yes. Both directions support HTTP/2. The client-to-proxy connection supports HTTP/1.1 and HTTP/2 over TLS 1.3 with X25519MLKEM768. gRPC backends are supported when Backend TLS is enabled.
PQ-Proxy falls back to X25519 (classical Diffie-Hellman) for clients that don't support post-quantum key exchange. The connection is still TLS 1.3. Chrome, Firefox, and curl support X25519MLKEM768 by default.
137.66.56.190
Yes, but set the Cloudflare proxy status to DNS only (gray cloud). If the orange cloud proxy is active, Cloudflare terminates TLS before reaching PQ-Proxy — disabling the post-quantum handshake.
No. PQ-Proxy uses TLS 1.3 over TCP. HTTP/3 (QUIC) is not supported — a deliberate choice, as UDP is frequently blocked in enterprise networks.
Install PQ-Proxy on your own server. Offline license verification with ML-DSA-65.
Self-service: onprem.fipsign.dev · API base: http://YOUR_SERVER_IP:9090/api/v1
| Requirement | Details |
|---|---|
| Server | Linux server with a public IP address. The installer runs as root in /root. |
| Docker Engine | Version ≥ 23 with Docker Compose v2. If not installed, the installer will install it automatically via get.docker.com. |
| Port 443 | Open inbound — TLS connections from your clients arrive here. |
| Port 80 | Open inbound — required only if you use ACME / Let's Encrypt for automatic certificate provisioning. |
| Port 9090 | Open inbound from your own IP — the management dashboard. Not exposed to the internet by default. |
| Outbound HTTPS | The proxy sends a heartbeat to proxyonprem.fipsign.dev every 24 hours. This must not be firewalled. |
| license.pqp | Your license file. Obtain a trial or purchase a license at onprem.fipsign.dev. |
Go to onprem.fipsign.dev/trial, enter your corporate email, and click Request trial. A verification link will be sent to your email. After clicking it, you will receive the license.pqp file at the same address. The download link is single-use and expires in 24 hours.
Go to onprem.fipsign.dev/purchase and complete the payment via OxaPay (crypto). The license.pqp file will be sent to your email after payment is confirmed. The download link is single-use and expires in 48 hours.
license.pqp file is a JSON document signed with ML-DSA-65 (NIST FIPS 204). Verification is 100% offline — the proxy binary has the public key embedded at compile time. The file cannot be forged or modified without invalidating the signature.# From your local machine scp license.pqp root@YOUR_SERVER_IP:/root/
# On your server, as root
curl -fsSL https://proxyonprem.fipsign.dev/install -o install.sh && bash install.sh
curl ... | bash). Download first so you can inspect the script before running it.| Question | Default | Notes |
|---|---|---|
| Organization name | — | Shown in the dashboard sidebar and alert emails. |
| Operator email | — | Used for SMTP alerts if configured. |
| Public URL | (blank) | Optional. If set (e.g. https://proxy.mycompany.com), alert emails include a direct link to the dashboard. |
| Database | 1 — Bundled PostgreSQL | Bundled is recommended. Option 2 allows an external PostgreSQL URL (AWS RDS, Supabase, Neon, etc.). |
| TLS port | 443 | Port where the post-quantum proxy listens for client connections. |
| HTTP port | 80 | Used for ACME HTTP-01 challenges and HTTP→HTTPS redirects. |
| Server exposure | 1 — Direct | 1 = direct to internet (socket IP). 2 = behind nginx/HAProxy/NLB (Proxy Protocol). 3 = behind Cloudflare/ALB/CDN (X-Forwarded-For). Affects how the real client IP is determined. |
| Allow private backends | Y | Y = backends on private LAN IPs (10.x, 192.168.x, 172.16–31.x) are accepted. N = SSRF protection is active — only public IPs and hostnames allowed. |
| Enable ACME | N | Y = Let's Encrypt certificates are provisioned automatically. Requires port 80 to be publicly reachable. N = upload certificates manually (BYOC) from the dashboard. |
| Session duration | 24 hours | How long a dashboard login session lasts before requiring re-authentication. |
| Configure SMTP | N | Y = enables OTP email login and email alerts. Requires host, port, username, password, from address, and TLS mode (starttls | tls | none). |
| Enable Prometheus | N | Y = exposes a Prometheus metrics endpoint. |
| Connection log retention | 30 days | Connection logs older than this are automatically purged. |
| Enable email alerts | N | Requires SMTP to be configured. Sends alerts on certificate expiry and license events. |
| Webhook URL | (blank) | Optional. Slack, PagerDuty, or any HTTP endpoint. Signed with HMAC-SHA256. |
| Cert expiry warning | 30 days | Alert is sent when a certificate expires in fewer than this many days. |
| Cert expiry critical | 7 days | Critical alert threshold. |
| Check for updates | Y | Y = the management API checks for new versions every 24 hours (configurable). Does not auto-update. |
| Update check interval | 24 hours | How often to poll for new versions. |
| File | Description |
|---|---|
/root/compose.yml | Docker Compose stack definition — three services: db (PostgreSQL), api (management-api + dashboard), proxy (proxy-core). |
/root/config.toml | Full configuration file. To change any setting, edit this file and run bash install.sh --reconfigure. |
/root/.env | Environment variables: INTERNAL_SERVICE_TOKEN, DB_PASSWORD, LICENSE_FILE, API_PORT. |
At the end of the installation, the terminal prints your admin token:
docker compose exec api pq-proxy-api reset-tokenhttp://YOUR_SERVER_IP:9090/dashboard
Log in by pasting the admin token. If SMTP is configured, you can also log in with an OTP sent to the operator email.
| Method | Requires | Description |
|---|---|---|
| Static token | Always available | Paste the admin token (pqp_onprem_...) in the login screen. Works without SMTP. |
| OTP email | SMTP configured | Enter the operator email — a 6-digit OTP valid for 10 minutes is sent. Rate limited to 5 attempts per 15 minutes per IP. |
# Requires an active dashboard session
POST /api/v1/admin/rotate-token
The old token is immediately invalidated. The new token is returned in the response — copy it before closing the session.
If you have lost the token and have no active session, run this command on your server via SSH. It requires no dashboard access, no SMTP, and no DB knowledge — just SSH to your server as root:
# On your server, as root
docker compose exec api pq-proxy-api reset-token
A new token is generated, saved to the database, and printed to the terminal. The old token is immediately invalidated.
POST /api/v1/admin/invalidate-sessions
Forces all active sessions to log out. Useful if you suspect a session was compromised.
[auth] session_duration_hours in config.toml and restarting the API container.| Field | Value | Notes |
|---|---|---|
| Public domain | api.yourcompany.com | Must be a valid FQDN (contains at least one dot). Labels must be ASCII alphanumeric or hyphens, max 63 chars each, max 253 total. |
| Backend host | 10.0.0.5 or backend.internal | Where the proxy forwards decrypted traffic. Private IPs (10.x, 192.168.x, 172.16–31.x) are allowed when allow_private_backends = true (the default). Set to false for SSRF protection. |
| Backend port | 443 | Default is 443. Use 80 or any other port for plain HTTP backends. |
| Backend speaks TLS | ☐ or ✓ | Enable if your backend serves HTTPS. The proxy will establish a TLS connection to the backend. Disable for plain HTTP backends. |
Point the domain's A record to your server's public IP:
Type: A Name: api Value: YOUR_SERVER_IP TTL: 3600
Enable ACME during installation or via reconfiguration. The proxy issues HTTP-01 challenges on port 80. Once a domain is added, the certificate is provisioned on the first connection. Renewal is checked every 12 hours — certificates are automatically renewed when they have fewer than 10 days remaining. The expiry date is read directly from the certificate X.509 field, so any certificate duration is supported.
http://api.yourcompany.com/.well-known/acme-challenge/....Go to Dashboard → Domains, click Upload BYOC on your domain, and paste your PEM-encoded certificate and private key.
| Field | Format | Notes |
|---|---|---|
| cert_pem | PEM | Full chain preferred — include intermediate certificates. The proxy parses the expiry date automatically. |
| key_pem | PEM | RSA or ECDSA. Must match the certificate. Stored encrypted at rest. |
# List all certificates with expiry dates
GET /api/v1/certificates
curl -sv https://api.yourcompany.com 2>&1 | grep -i "ssl\|tls\|x25519"
Open your domain in Chrome or Brave, press F12, go to Security tab. The connection line should show TLS 1.3, X25519MLKEM768, and AES_128_GCM.
The proxy automatically falls back to X25519 (classical Diffie-Hellman). The connection is still TLS 1.3 — just without the post-quantum key exchange. No configuration needed.
Every time the proxy-core container starts, it reads license.pqp from disk and verifies the ML-DSA-65 signature against the public key embedded in the binary. If the signature is invalid or the file is missing, the proxy does not start.
| State | Condition | Behavior |
|---|---|---|
| valid | More than 30 days until expiry | Full operation. No warnings. |
| warning | Less than 30 days until expiry | Full operation. Dashboard shows a renewal warning. |
| grace | Expired, within 30 days after expiry | Full operation. Dashboard shows an urgent warning. Renew before grace ends. |
| expired | More than 30 days past expiry | The proxy stops serving domains — internal_list_domains returns an empty list. No new connections are accepted. |
The proxy-core sends a heartbeat to proxyonprem.fipsign.dev/v1/heartbeat every 24 hours. The heartbeat includes the license_key and the current proxy version. FIPSign validates the license and responds with {"ok": true} if it is valid, or {"ok": false, "status": "revoked"} if it has been revoked.
| Scenario | Behavior |
|---|---|
| Heartbeat OK | Negative streak counter resets to 0. Normal operation continues. |
| 3 consecutive negative heartbeats | Proxy calls exit(1) — the container stops. This triggers a Docker restart if the restart policy is set (default: always). On the next start, the heartbeat is retried. |
| 96 hours without a successful heartbeat | Proxy calls exit(1). This prevents indefinite operation with a firewalled heartbeat endpoint. |
| Network error or HTTP error | Logged as a warning. Does not increment the negative streak — only explicit ok: false responses do. |
proxyonprem.fipsign.dev. The proxy will stop after 96 hours without a successful heartbeat.Go to Dashboard → License and click Re-verify. This re-reads the license.pqp file from disk and updates the verified_at timestamp in the database.
SMTP is configured during installation. To add or change it later, run bash install.sh --reconfigure and answer Y to the SMTP question.
| Field | Example | Notes |
|---|---|---|
| SMTP host | smtp.resend.com | Any SMTP provider — Resend, SendGrid, Postmark, Gmail, etc. |
| SMTP port | 587 | 587 (STARTTLS), 465 (TLS), or 25. |
| Username | resend | SMTP authentication username. |
| Password | — | Stored in config.toml, not exposed via any API endpoint. |
| From address | PQ-Proxy <[email protected]> | Sender address for alert emails. |
| TLS mode | starttls | starttls | tls | none |
| Event | Channel | Condition |
|---|---|---|
| Certificate expiry warning | Email + Webhook | Certificate expires in fewer than cert_expiry_warning_days (default: 30 days) |
| Certificate expiry critical | Email + Webhook | Certificate expires in fewer than cert_expiry_critical_days (default: 7 days) |
| License warning | Dashboard banner | License expires in fewer than 30 days |
| License grace | Dashboard banner | License expired — within the 30-day grace period |
Set a Webhook URL during installation. The payload is a JSON POST signed with HMAC-SHA256 using the webhook_secret from config.toml. Verify the signature in your handler with the X-Webhook-Signature header.
# Example webhook payload
{
"event": "cert_expiry_warning",
"domain": "api.yourcompany.com",
"expires": "2026-07-25T10:00:00Z",
"days": 5
}
Answer Y to Enable Prometheus endpoint? during the installer wizard. This sets prometheus_enabled = true in config.toml.
http://YOUR_SERVER_IP:9090/metrics
# prometheus.yml scrape_configs: - job_name: pqproxy-onprem static_configs: - targets: ['YOUR_SERVER_IP:9090']
POST /api/v1/auth/token and pass it in the Cookie header of the scrape request.Go to Dashboard → Audit Log to browse recent events with timestamps, actor IP, and action details.
GET /api/v1/audit-log
GET /api/v1/audit-log/export
Downloads the full audit log as a CSV file. Useful for compliance and external auditing.
GET /api/v1/metrics/connections
GET /api/v1/metrics/latency
GET /api/v1/connections/export
Downloads all connection logs within the retention window (configured at install time, default 30 days) as a CSV file. Each row is one TLS connection with fields: id, domain, peer_addr, bytes_sent, bytes_received, algorithm, connected_at, duration_ms, tls_handshake_ms, backend_connect_ms.
X25519MLKEM768 when the client supports post-quantum key exchange, or X25519 for classical TLS 1.3 fallback.If automatic update checks are enabled (default: every 24 hours), the dashboard shows a notification when a new version is available. You can also check manually:
GET /api/v1/updates/check
# On your server, as root
docker compose pull && docker compose up -d
This pulls the latest api and proxy images from GHCR and restarts the containers. The database is not affected — all configuration and data is preserved. Downtime is typically under 5 seconds.
db container (PostgreSQL) is not updated by docker compose pull unless the image tag changes. Major PostgreSQL version upgrades require a separate migration procedure.curl -fsSL https://proxyonprem.fipsign.dev/install -o install.sh && bash install.sh --reconfigure
The wizard shows the current value of every setting as the default — press Enter to keep it, or type a new value. The database configuration is preserved automatically (cannot be changed via the wizard — edit config.toml manually if needed).
| Preserved | Notes |
|---|---|
INTERNAL_SERVICE_TOKEN | Read from .env — not regenerated. Changing it would break communication between containers. |
| Database URL and password | Read from config.toml. The database is not touched. |
| All domains and certificates | Stored in PostgreSQL — unaffected by reconfiguration. |
| Not preserved | Notes |
|---|---|
| Admin token | The token is stored in the database, not in config.toml — it is never regenerated by the wizard. Rotate it manually from the dashboard if needed. |
| Active sessions | Restarting the API container invalidates all in-memory session state. Users are redirected to the login page. |
# Edit the configuration file nano /root/config.toml # Apply changes by restarting the API container docker compose restart api
Changes to proxy settings (ports, real_ip_source, allow_private_backends) also require restarting the proxy container: docker compose restart api proxy.
/api/v1/auth/*.After logging in, the API sets a session cookie. All authenticated requests use this cookie automatically in the browser. For programmatic access, use the token login endpoint to obtain a session cookie, then include it in subsequent requests.
# Login with static token curl -c cookies.txt -s \ -X POST http://YOUR_SERVER_IP:9090/api/v1/auth/token \ -H "Content-Type: application/json" \ -d '{"token": "pqp_onprem_..."}' # Use the session cookie in subsequent requests curl -b cookies.txt -s \ http://YOUR_SERVER_IP:9090/api/v1/license
| Method | Path | Description |
|---|---|---|
| POST | /api/v1/auth/token | Login with static admin token. Body: {"token": "pqp_onprem_..."} |
| POST | /api/v1/auth/otp/send | Send OTP to operator email. Requires SMTP. Body: {"email": "..."} |
| POST | /api/v1/auth/otp/verify | Verify OTP and create session. Body: {"otp": "123456"} |
| POST | /api/v1/auth/logout | Invalidate current session. |
| GET | /api/v1/auth/me | Returns current session info. |
| GET | /api/v1/instance | Returns instance configuration (name, email, version). |
| GET | /api/v1/license | Returns license details and current status. |
| POST | /api/v1/license/verify | Re-reads license.pqp from disk and updates the database. |
| GET | /api/v1/domains | List all configured domains. |
| POST | /api/v1/domains | Add a domain. Body: {"domain","backend_host","backend_port","backend_tls"} |
| PUT | /api/v1/domains/:id | Update a domain's backend. Body: {"backend_host","backend_port","backend_tls"} |
| PATCH | /api/v1/domains/:id/toggle | Enable or disable a domain without deleting it. |
| DELETE | /api/v1/domains/:id | Delete a domain and its certificate. |
| GET | /api/v1/certificates | List all certificates with source and expiry. |
| POST | /api/v1/certificates/:domain | Upload BYOC certificate. Body: {"cert_pem","key_pem"} |
| DELETE | /api/v1/certificates/:domain | Remove a BYOC certificate (reverts to ACME if enabled). |
| GET | /api/v1/updates/check | Check if a new version is available. |
| GET | /api/v1/audit-log | List audit log entries. |
| GET | /api/v1/audit-log/export | Download audit log as CSV. |
| GET | /api/v1/metrics/connections | Connection count and byte totals. |
| GET | /api/v1/metrics/latency | TLS handshake and backend connect latency averages. |
| GET | /api/v1/connections/export | Download connection logs as CSV. |
| POST | /api/v1/admin/rotate-token | Generate a new admin token. Requires active session. |
| POST | /api/v1/admin/invalidate-sessions | Invalidate all active sessions. Requires active session. |
Yes. The license.pqp file contains a JSON payload signed with ML-DSA-65 (NIST FIPS 204). The proxy binary has the FIPSign public key embedded at compile time. Verification requires no network access — the signature is checked against the embedded key on every startup.
If SMTP is configured, log in with an OTP and rotate the token from the dashboard. If SMTP is not configured and you have no active session, run this command on your server via SSH:
docker compose exec api pq-proxy-api reset-token
This generates a new token, saves it to the database, and prints it to the terminal. No manual database access required. The old token is immediately invalidated.
The proxy continues operating normally for 30 days after the license expires. All existing connections are served. You can add new domains. After the grace period ends, internal_list_domains returns an empty list — the proxy stops routing traffic until a valid license is installed.
Purchase a new standard license at onprem.fipsign.dev/purchase. Download the new license.pqp file, copy it to the server at /root/license.pqp, and click Re-verify in the dashboard. No restart required — the new license is loaded immediately.
The license file does not enforce a specific server. However, each license is intended for use within a single organization. Running the same license on servers belonging to different organizations violates the terms of service and may result in revocation.
# Stop and remove all containers and volumes docker compose down -v # Remove configuration files rm -f /root/compose.yml /root/config.toml /root/.env /root/install.sh
docker compose down -v deletes the PostgreSQL volume and all data — domains, certificates, audit logs, and connection history. This is irreversible.