On this page
Key takeaways
- Self-hosting makes you responsible for the whole security surface; a layered baseline is affordable and effective.
- Reduce what is reachable, reduce what a compromise can do, and make abnormal activity loud.
- Secrets, database access, and the signing path deserve the most care; backups and restores deserve a drill.
- Security is a routine: patching, rotation, and review on a schedule, with named owners.
When you self-host a payment gateway, security stops being a line in a vendor's trust page and becomes your own engineering. That is the point — you get to inspect and control it — and also the responsibility. The good news is that a hardened deployment is not exotic. It is a layered set of ordinary controls applied consistently.
This guide is written for the engineer who has just been handed a gateway to deploy. It is not a compliance framework, and it does not replace a review by your own security team. It is a practical baseline organised by layer, with the reasoning for each control so you can adapt it.
A mental model: reduce, contain, detect
| Goal | Question | Examples |
|---|---|---|
| Reduce exposure | What can an attacker reach? | Private networks, minimal open ports, allow-lists |
| Contain impact | If something is compromised, what can it do? | Least privilege, separate credentials, read-only filesystems |
| Detect and recover | Will we notice, and can we rebuild? | Logging, alerts, tested backups, rotation |
Layer 1: the network
Network baseline
- Only the intended endpoints (checkout, API, webhook return path) are reachable from the internet.
- The database has no public address and accepts connections only from the gateway's network.
- The dashboard and admin routes are reachable only through a VPN, allow-list, or single sign-on.
- TLS terminates at a hardened edge with modern protocol settings; no plaintext internal secrets.
- Egress is restricted where practical: the gateway needs to reach RPC providers and your webhook targets, not the whole internet.
| Surface | Recommended posture |
|---|---|
| Public API | Rate-limited, authenticated, input-validated [4] |
| Checkout | Public, but minimal; no secrets, no admin functions |
| Operator dashboard | Private; single sign-on with multi-factor authentication |
| Database | Private network only; strong authentication [5] |
| RPC providers | Outbound only; credentials in the secret store |
Layer 2: containers and hosts
A container is not a security boundary by itself, but sensible defaults shrink what a compromise can do. The OWASP Docker guidance [1] covers this in depth; these are the highest-value items.
FROM node:22-slim AS build
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN corepack enable && pnpm install --frozen-lockfile
COPY . .
RUN pnpm build
FROM node:22-slim
ENV NODE_ENV=production
WORKDIR /app
COPY --from=build /app/.next ./.next
COPY --from=build /app/node_modules ./node_modules
COPY --from=build /app/package.json ./
USER node # never run as root
EXPOSE 3000
HEALTHCHECK --interval=30s --timeout=3s CMD node -e "fetch('http://localhost:3000/healthz').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"
CMD ["node", "node_modules/next/dist/bin/next", "start"]services:
gateway:
image: registry.example.com/gateway:1.4.2 # pin a version, never :latest
read_only: true # immutable root filesystem
tmpfs: ["/tmp"]
user: "node"
cap_drop: ["ALL"] # no extra Linux capabilities
security_opt: ["no-new-privileges:true"]
environment:
DATABASE_URL_FILE: /run/secrets/database_url # secrets from files, not env dumps
secrets: [database_url]
networks: [internal]
deploy:
resources:
limits: { cpus: "1.0", memory: 512M }- Pin image versions and verify digests. A moving tag means you deploy different code without deciding to.
- Scan images. Run a vulnerability scanner in the pipeline and fail on serious findings with a fixable version.
- Keep hosts minimal and patched. Fewer packages, automatic security updates, and an owner for the base image.
- Limit resources. A runaway process should hurt itself, not its neighbours.
Layer 3: secrets
Secrets are the crown jewels: database credentials, webhook secrets, RPC keys, and above all anything involved in signing. The OWASP secrets-management guidance [2] and the twelve-factor principle of configuration in the environment [7] agree on the essentials, refined by modern practice: prefer short-lived, narrowly scoped credentials pulled at runtime over long-lived values baked into images or files.
| Practice | Why | How |
|---|---|---|
| No secrets in repositories or images | They persist in history and layers | Secret store; scan commits for secrets |
| Load at runtime | Keeps them out of build artefacts | Mount from the secret manager |
| Least privilege | Limits the blast radius | One credential per service, per environment |
| Rotate on a schedule and on events | Shrinks the window of a leak | Automate; drill the procedure [8] |
| Separate environments | A staging leak must not matter | Different secrets and stores per environment |
| Audit access | You must see who read what | Log secret reads; alert on anomalies |
Layer 4: the database
Payment state lives in PostgreSQL. It deserves authentication, least privilege, encryption, and above all, backups you have actually restored.
-- the application role can read and write its own tables, nothing else
CREATE ROLE gateway_app LOGIN PASSWORD :'app_password';
GRANT CONNECT ON DATABASE gateway TO gateway_app;
GRANT USAGE ON SCHEMA public TO gateway_app;
GRANT SELECT, INSERT, UPDATE ON ALL TABLES IN SCHEMA public TO gateway_app;
REVOKE DELETE, TRUNCATE ON payments, payment_events FROM gateway_app; -- history is append-only
-- a separate role runs migrations; the app role cannot alter schema
CREATE ROLE gateway_migrator LOGIN PASSWORD :'migrator_password';
GRANT ALL ON SCHEMA public TO gateway_migrator;| Control | Detail |
|---|---|
| Authentication | Strong, certificate- or scram-based authentication; restrict by network [5] |
| Encryption in transit | Require TLS for all connections |
| Encryption at rest | Encrypted volumes and encrypted backups |
| Append-only history | Revoke delete on audit and event tables from the app role |
| Connection limits | Prevent connection exhaustion |
Backups and restores
A backup you have never restored is a hope. Continuous archiving and point-in-time recovery [6] let you rebuild to a chosen moment, which matters when the problem is a bad change rather than a dead disk.
- 01
Take base backups on a schedule
Plus continuous archiving of write-ahead logs.
- 02
Encrypt and store them elsewhere
A different account or region, with separate credentials.
- 03
Restore into a scratch environment monthly
Time it. Verify the data and run reconciliation against it.
- 04
Write the recovery procedure down
Including who decides and how long recovery should take.
- 05
Alert on backup failures
A silent backup failure is discovered at the worst time.
Layer 5: the application
- Validate all input. Treat every request field as untrusted; the API security top ten [4] is a good checklist for object-level authorisation, rate limiting, and mass assignment.
- Verify webhook signatures on the raw body. And enforce timestamp tolerance.
- Authenticate operators properly. Single sign-on, multi-factor authentication, and role-based access to dangerous actions.
- Log security-relevant events. Logins, permission changes, signature requests, and replays, to storage the application cannot alter.
- Handle errors safely. No stack traces or secrets in responses.
The signing path is special
Anything that can request a signature deserves extra controls: a narrow interface, policy checks, approval requirements, and alerts on unusual use. See the signing-boundary post for the design.
Layer 6: supply chain and change
| Risk | Control |
|---|---|
| Vulnerable dependency | Automated dependency updates and scanning; a patch cadence |
| Malicious or compromised package | Lockfiles, pinned versions, review of significant updates |
| Tampered image | Signed images and digest pinning; a trusted registry |
| Unreviewed change to production | Reviewed pull requests, protected branches, a deployment audit trail |
| Stale vendor software | Subscribe to the vendor's release notes; apply patch releases promptly |
Layer 7: monitoring and alerting
Hardening reduces the chance of an incident. Monitoring decides how long one lasts. Alert on things that should be rare, not on everything.
| Signal | Why it matters |
|---|---|
| Signing requests outside policy or hours | Possible misuse or compromise |
| New admin users or role changes | Privilege escalation |
| Repeated authentication failures | Credential stuffing or probing |
| Unexpected outbound connections | Exfiltration or malware |
| Indexer lag, webhook failures, unmatched payments | Operational health that also signals attack |
| Backup and certificate expiry | Preventable outages |
A hardening review you can run this week
Quarterly review
- Confirm what is exposed to the internet, and remove anything unintended.
- Verify the database is not publicly reachable and roles are least-privilege.
- Rotate one secret end to end, following the runbook.
- Restore last night's backup into a scratch environment.
- Check image and dependency scan results; apply pending patch releases.
- Review who has access to production, the secret store, and signing.
- Test one alert by causing the condition on purpose.
Application security verification standards [3] give a more formal way to structure such a review as your programme matures. Start with the baseline above, own it with names and dates, and improve it deliberately. A boring, consistently applied deployment is far safer than an ambitious one nobody maintains.
References & further reading
- 1Docker Security Cheat Sheet — OWASP Cheat Sheet Series
- 2Secrets Management Cheat Sheet — OWASP Cheat Sheet Series
- 3
- 4OWASP API Security Top 10 (2023) — OWASP
- 5PostgreSQL Documentation: Client Authentication — PostgreSQL Global Development Group
- 6PostgreSQL Documentation: Continuous Archiving and Point-in-Time Recovery (PITR) — PostgreSQL Global Development Group
- 7The Twelve-Factor App — Adam Wiggins, 12factor.net
- 8NIST SP 800-57 Part 1 Rev. 5: Recommendation for Key Management — Elaine Barker, NIST, 2020
The product behind this post
StablePay
Self-hosted stablecoin payment infrastructure.
From $4,800 one-time license
About the author
Product behaviour described here reflects what is implemented and tested; anything else is marked as planned. Code samples are illustrative.
All writing