When you are launching a startup, the pressure is intense. You need to validate your product idea immediately, which usually means keeping infrastructure lean. There is no budget for massive distributed cloud setups, managed relational databases, or complex Kubernetes clusters. You pick a single reliable virtual machine, choose a fast backend like PocketBase, build your MVP, and push it live.
But the moment your server hits the public web, a different countdown begins. Within ten minutes of spinning up a fresh instance, automated script scanners will find your public IP address. Your tiny startup machine is suddenly bombarded with internet background noise: port probes, malicious exploits, and automated bots crawling for database panels. If you run a single-binary backend out in the open, your administration console (/_) is sitting right on the frontline waiting for a brute-force attack.
A single well-timed exploit or an unmitigated denial-of-service bot could crash your lone server, wiping out your product validation phase before it even starts.
Standard reverse proxies don’t solve this. If you block an unauthorized user, Nginx serves a standard 403 Forbidden page. To an automated bot, a 403 is a massive green flag—it explicitly confirms that a sensitive directory exists, it’s just locked.
We want our single-box setup to be isolated by default. If someone is not an authorized user or an explicit administrator, infrastructure shouldn’t even validate its own existence.
To achieve this, we decoupled the data layer entirely from the host network. Put PocketBase inside an isolated internal Docker bridge with zero mapped host ports, dropped a programmable OpenResty edge gateway in front of it, and hardened the host access layer using Ubuntu 26.04’s native hybrid post-quantum SSH ciphers.
The entire workflow toggles between an offline local dev VM and the live production VPC using a single, unified Ansible playbook.
The Architecture: Keeping the Core Invisible#
When your entire startup relies on one machine, resource efficiency is as critical as security. We kept our stack aggressively lean. PocketBase runs inside an Alpine Linux container under a low-privileged system profile. In front of it sits OpenResty, acting as our high-performance programmable shield. Background lifecycle updates for TLS layers are handled quietly by an isolated Certbot instance scheduling daily evaluation checks.
We made the explicit architectural decision to enforce local machine access only for the PocketBase backend. By omitting the ports block entirely from the backend service in Docker Compose, the database engine is fundamentally severed from the host machine’s public interfaces. It has no idea the public internet exists; it only listens and responds to the internal, isolated bridge network shared exclusively with OpenResty. If the edge gateway doesn’t explicitly authorize a packet, it physically cannot reach the database.
Restructuring the Edge: Subdomains and IP Whitelisting#
By default, PocketBase exposes its application framework under a nested route format (https://domain.com/api). We broke this standard behavior by remapping the entire API interface directly to its own clean subdomain root: https://api.domain.com.
This isn’t just about aesthetic preferences. Moving the API engine to a dedicated subdomain isolates cookies, breaks potential Cross-Site Scripting (XSS) inheritance paths from the main user-facing application frontend, and allows us to apply aggressive, high-utility caching and network boundaries specifically tailored for rapid database traffic.
At the same time, we placed a strict IP whitelisting lock over the administrative console (/_). The dashboard pane is an incredibly high-value target for attackers. By restricting it to verified administrator public WAN IPs at the gateway layer, we ensure that even if a zero-day vulnerability is discovered in PocketBase’s core login screens tomorrow, an attacker cannot exploit it simply because they cannot route a single byte to the login handler.
Driving the Shield with Lua & Two-Tier Rate Limiting#
To enforce these rules dynamically without crippling Nginx’s blazing speed, we offload the logic to OpenResty’s embedded Lua runtime environment on server boot. Instead of running heavy regex lookups on every single connection, we grab our verified configurations directly from the system variables on startup:
init_by_lua_block {
global_domain = string.lower(os.getenv("DOMAIN_NAME"))
global_admin_ips = os.getenv("ADMIN_TRUSTED_IPS") or ""
}At the same time, we define two separate rate-limiting buckets inside the global space:
# Public API bucket: 5 requests per second steady-state
limit_req_zone $binary_remote_addr zone=api_limit_zone:10m rate=5r/s;
# Admin bucket: Strict 1 request per second steady-state
limit_req_zone $binary_remote_addr zone=admin_limit_zone:10m rate=1r/s;
limit_req_status 429;The matching edge locations intercept incoming requests and process them through a two-step validation chain. First, we enforce a lowercase matching block against ngx.var.host to completely eliminate common Host-Header injection tricks and raw IP scans. Second, we parse the client’s connection origin and apply the respective rate limit zone.
If either check fails, we invoke Nginx’s custom 444 No Response trigger. The proxy drops the TCP stream immediately, sending exactly zero bytes, zero payload headers, and zero server details back to the client. If they breach the rate-limit bucket, they get hit with an explicit HTTP 429 Too Many Requests:
location /_ {
access_by_lua_block {
if string.lower(ngx.var.host) ~= global_domain then ngx.exit(444) end
local ip = ngx.var.remote_addr
local allowed = false
for addr in string.gmatch(global_admin_ips, "[^,]+") do
if addr:match("^%s*(.-)%s*$") == ip then
allowed = true
break
end
end
if not allowed then
ngx.log(ngx.ERR, "Admin probe blocked from IP: ", ip)
ngx.exit(444)
end
}
# Enforces the 1r/s limit with a tiny burst window for asset loads
limit_req zone=admin_limit_zone burst=3 nodelay;
proxy_pass http://pocketbase:8090;
}Future-Proofing the Host#
Running this layout on Ubuntu 26.04 gives us a major cryptographic advantage right out of the box: OpenSSH v10.2.
OpenSSH v10.0+ natively promoted hybrid post-quantum key exchange algorithms to the standard system default. By wrapping traditional, battle-tested elliptic curve groups (X25519) inside lattice-based cryptography (ML-KEM-768), our administrative terminal link is completely safe from “Harvest Now, Decrypt Later” quantum intercept models.
To enforce this globally across our cloud nodes, our Ansible playbook modifies the host’s ssh daemon configuration, drops legacy block ciphers, strips out vulnerable classical MAC layers, and locks down terminal handshakes exclusively to post-quantum channels:
- name: Restrict Host Key Exchanges to Post-Quantum Hybrid Algorithms
lineinfile:
path: /etc/ssh/sshd_config
regexp: '^#?KexAlgorithms'
line: 'KexAlgorithms mlkem768x25519-sha256,sntrup761x25519-sha512@openssh.com'
notify: Restart SSH DaemonOrchestration and the Tag Strategy#
The entire lifecycle is coordinated via a centralized Ansible playbook. Because running large host configurations from the beginning gets slow and repetitive over time, we split our setup into modular, highly targeted Tags. This allows us to bypass the entire server setup loop and invoke specific infrastructure tasks using the --tags argument:
--tags "sync": Instantly mirrors modifications made inside the local text configuration assets (.envornginx.conf) and syncs custom server-side JavaScript app hooks safely.--tags "backup": Triggers an on-demand, timestamped pre-upgrade backup snapshot archive of your SQLite database directory, calculates retention limits, and automatically prunes older archives to protect host disk volume space.--tags "security": Isolates and enforces the post-quantum SSH host keys, MACs, and symmetric ciphers without touching container states.
Closing Thoughts#
No single server or setup is entirely bulletproof. If a highly motivated, well-funded adversary with a zero-day exploit targets your infrastructure specifically, a few lines of Lua code won’t magically stop them.
But that isn’t what this layout is trying to solve. Security in a single-server startup is about raising the cost of entry for attackers. By implementing these core best practices, severing your core database from public host interfaces, validating headers at the network edge, and enforcing post-quantum transport safety, you eliminate most of the automated script noise that tanks unhardened boxes.
Rate limiting says, “You can try to exploit this endpoint 5 times a second.” OpenResty dynamic Lua validation says, “If you hit my network interface without matching my clean domain header token, your connection ceases to exist right now.”
It’s a low-overhead, highly performant way to shift the odds drastically in your favour. By keeping your data layer quiet, unsearchable, and securely hidden in the dark, you buy your startup the runway it needs to focus entirely on finding product-market fit.