Type something to search...
Leaving AWS for Hetzner: What You Have to Rebuild

Leaving AWS for Hetzner: What You Have to Rebuild

Moving from AWS to Hetzner keeps compute, block storage, private networking, load balancers, DNS and object storage, and replaces the managed database, the managed Kubernetes control plane, IAM, CloudWatch, Secrets Manager, autoscaling groups, WAF and AWS Backup with components you select and run. Everything in that second list has to exist before the environment is production-grade, and it is the part that is easiest to under-scope, because on AWS most of it arrives without being asked for.

This is the concrete inventory: seven layers, what each one replaces, and the order to build them in. It applies to any bare-VM provider; the specifics below are Hetzner’s.

A note on tooling before the layers: the official hetznercloud/hcloud Terraform provider covers servers, networks, subnets, firewalls, load balancers, volumes, snapshots, placement groups, primary IPs and certificates — which is very close to the whole Hetzner API, because that API is small. It works identically under OpenTofu, and for a platform sold on portability that is the version worth defaulting to. What it does not cover is everything from Layer 2 upward, which is Ansible and Helm. On AWS, Terraform is most of the platform; here it is roughly the bottom third.

Layer 0 — Foundation: the isolation boundary is the project

Hetzner has no IAM. An API token is scoped to one project and is either read-only or read-write, and that is the entire permission model. There are no roles, no policies, no conditions, no resource-level grants.

Three consequences follow directly, and none of them are optional:

  • One project per environment. acme-prod, acme-stage, acme-dev as separate Hetzner projects. This is the only blast-radius control the platform offers, so a leaked staging token cannot reach production only if production is a different project.
  • One token per consumer. Terraform for production, Terraform for staging, CI, and a read-only token for monitoring — each stored in the secrets manager with a named rotation owner. The Hetzner API is also rate-limited per token, so a CI job running terraform plan on every pull request gets its own rather than sharing with anything that matters.
  • Remote state with locking, from the first commit. An S3 backend on Hetzner Object Storage keeps state in the same provider and jurisdiction, with versioning enabled. Locking is what allows more than one engineer to run applies against the same environment. Hetzner’s S3 is not AWS’s, so the backend needs use_lockfile = true plus skip_credentials_validation, skip_requesting_account_id and skip_metadata_api_check; native locking depends on the object store honouring conditional writes, which is worth proving with two concurrent applies rather than assuming.

One foundation item does more work than it appears to: a naming and labelling convention, enforced in CI. {client}-{env}-{role}-{ordinal} for names, and every resource carrying env, role, owner, managed-by and cost-center labels. Labels are the mechanism by which firewalls attach themselves to new servers and the monitoring stack discovers targets, and they are what makes the Hetzner invoice attributable per environment and per role. Because they are functional rather than descriptive, a missing label is treated as a pipeline failure.

Layer 1 — Network and edge

The shape that works is a single private network per project, subnets split by role, and all internal traffic on private addresses only.

Hetzner Cloud Firewalls are default-deny and attach by label selector, which means a server is protected the moment it boots rather than the moment someone remembers to attach a rule. Public ingress is 80 and 443 to the edge nodes and nothing else. Port 22 is not open to the internet at all; SSH arrives through the access plane described in Layer 5.

Run a host firewall as well — nftables or ufw, configured by Ansible. Two independent layers, so that neither the cloud firewall nor the host firewall is the sole control.

Two Hetzner specifics worth designing around early:

  • Public IPv4 addresses are billed per address, currently €0.50 per month each. An IPv6-only internal fleet with a small egress node is the design that avoids paying for addresses nothing routes to.
  • Outbound port 25 is blocked by default. Transactional email goes through a provider — SES, Postmark, Resend — so the sending path belongs in the initial design.

For ingress and TLS, Traefik or Caddy on an edge node handles ACME and L7 routing without adding a billed resource, in exchange for owning that node’s availability. A Hetzner Cloud Load Balancer adds health checks and removes the single edge box, with a thinner L7 feature set and no WAF. The combination used at the larger tiers is a Cloud Load Balancer in front, Traefik on the nodes for L7 routing, Cloudflare in front of both for WAF and rate limiting, and a Cloudflare Tunnel for internal admin interfaces so those carry no inbound ports.

Layer 2 — Runtime

The OS baseline is built once by cloud-init and then owned by an Ansible role, never by hand: unattended upgrades, SSH with no passwords and no root login, nftables, auditd with a shipped ruleset, journald limits, a node exporter and log agent, a non-root deploy user, and rootless-capable Docker.

The container platform is the largest single decision. The useful selection criterion is the operating experience the team already has, since Hetzner supplies no control plane for any of them:

  • Coolify suits small teams that want a working git-push-to-deploy flow quickly and want to operate one component rather than five. One property shapes the DR plan: application definitions and environment variables live in Coolify’s own database, so that database dump is part of what has to be backed up for the environment to be reproducible.
  • k3s suits teams with existing Kubernetes operating experience, and provides scheduling, horizontal autoscaling, GitOps and admission policy. Because there is no managed control plane, etcd, upgrades and certificate rotation sit with the platform team.
  • Nomad suits mid-size estates with mixed container and binary workloads. Substantially simpler to operate than Kubernetes, with a smaller ecosystem around it.
  • Docker Compose driven by Ansible suits single-application deployments. Minimal platform surface to patch or upgrade, and no scheduling or self-healing beyond restart policies.

Layer 3 — CI/CD

The pipeline itself is unremarkable and should be: lint and test, build with BuildKit, generate an SBOM, scan the image and filesystem, sign with cosign, push to a registry, deploy, verify, and annotate the deploy into your dashboards.

Three rules carry most of the value:

  • Deploy by image digest, never by mutable tag. A tag can be moved and a digest cannot, which makes rollback deterministic and the audit trail exact.
  • Migrations run as an explicit, idempotent, backward-compatible step before the new version takes traffic, so that rolling back the application does not require rolling back the database.
  • Rollback is a documented command, rehearsed once per engineer as part of onboarding rather than first used under load.

Run ephemeral self-hosted runners inside the private network: one job per runner, destroyed afterwards. Deploys then need no public ingress at all, and this is the layer where the cheaper compute shows up soonest, since build minutes are usually the largest variable line in a CI bill. Untrusted pull-request builds run on runners that hold no credentials.

For secrets in the pipeline, federate CI to the secrets manager over OIDC so CI holds no long-lived credentials. This keeps one copy of each credential, rather than separate copies in CI, in a platform UI and in a .env file on a host.

Layer 4 — Observability

This is the layer that replaces CloudWatch, and it needs a decision rather than a default.

Establish the contract before choosing a backend: every service emits OTLP traces and metrics plus structured JSON logs to stdout, carrying a correlation ID, to a collector running locally on the host, and no service embeds a vendor SDK. That rule is what keeps the backend swappable without touching application code, and it is cheaper to apply at the start than to retrofit.

For the backend, the Grafana stack — Alloy, Loki, Prometheus or Mimir, and Tempo — offers the strongest dashboards and cheap object-storage retention, across four or five components to run and tune. SigNoz is a single ClickHouse-backed application covering traces, metrics and logs with correlation built in: less operational surface, a smaller dashboard ecosystem. Team size and appetite for operating the stack is the criterion; a team standing up its first self-hosted observability stack generally takes SigNoz.

The content that ships alongside matters as much as the choice: per-service golden signals, SLOs with burn-rate alerts written against symptoms rather than raw CPU, alert routing with a documented on-call rota, and PII scrubbing at the collector. Include one external prober — Better Stack, Healthchecks.io or similar — since a monitor inside the estate cannot report that the estate is unreachable.

Layer 5 — Security

The access plane replaces IAM for people. Teleport issues short-lived certificates for SSH, Postgres and web applications behind one identity with SSO and MFA, and records sessions, which is what answers “who reached production, and when” for an auditor. It is itself a component to keep running, so it ships with a documented break-glass path. Tailscale or Headscale with identity-based ACLs is simpler to operate with less audit depth. WireGuard plus an SSH certificate authority has the fewest moving parts and no vendor dependency, with the audit trail assembled from host logs.

Common to all three: no long-lived private keys on laptops, no shared accounts, an offline break-glass credential with an expiry, a quarterly access review, and an alert on any direct root SSH.

For secrets, one source of truth that everything else references. Infisical self-hosted covers most cases with per-environment RBAC and good CI integration; Vault fits where dynamic credentials and PKI are needed and the operational weight is acceptable; SOPS with age fits a pure GitOps workflow, with rotation and audit handled outside the tool. The constant across all three is that a secret is authored in the store, and every other location references it.

The rest of the layer is a CIS-informed Ansible baseline, automatic security patching with a reboot window, CrowdSec at the ingress for behavioural blocking, containers running rootless with read-only root filesystems and dropped capabilities, and dependency and image scanning in the pipeline. Cloudflare’s WAF covers the edge; Coraza on Traefik covers it if Cloudflare is not acceptable.

Layer 6 — Data and disaster recovery

Hetzner provides no managed database, so backups, replication and recovery are all explicit deliverables here rather than defaults. This is the layer that most often gets scoped last and takes the longest to get right.

The Postgres shape scales with the tier: a single instance with PgBouncer at the smallest, a primary with a streaming replica on a separate host and a documented promotion procedure in the middle, and CloudNativePG or Patroni for automated failover at the top. Route every application through PgBouncer, and give every connection pool an error handler, so that a brief pool exhaustion surfaces as a handled error rather than as a restart loop.

Backups follow 3-2-1 and the details matter:

  • pgBackRest doing weekly full backups plus continuous WAL archiving to object storage, which is what gives you point-in-time recovery rather than last-night’s-dump recovery.
  • Restic or Kopia for volumes and configuration.
  • Hetzner snapshots and server backups as the fast local restore path.
  • At least one copy off-provider, with object lock or immutability, written by a credential that production cannot delete with. Same-provider snapshots cover hardware and operator error; an off-provider immutable copy is what covers provider-level loss and ransomware.

Where Coolify is the runtime, its own database is backed up on the same cadence as application data, since it holds the application definitions the environment is reconstructed from.

Then state an RPO and RTO per environment, agree them in writing, and run a timed restore drill every quarter. The measured restore time is the output that turns a target RTO into a demonstrated one, and it is the most directly useful artefact this layer produces.

The order to build in

Build bottom-up. Each layer is independently useful, so work that stops at a boundary still leaves a complete platform at that level.

  1. Foundation and network — projects, tokens, state, private network, default-deny firewalls, OS baseline, access plane. Done when an engineer can reach production with a short-lived credential and nothing else can.
  2. Runtime and CI/CD — container platform, registry, runners, first application deployed by digest. Done when a commit reaches production through the pipeline and back out again.
  3. Observability — agents, backend, dashboards, SLOs, alert routing. Done when a deliberately broken service pages the right person with a useful message.
  4. Security and data — secrets consolidation, hardening, WAF, scanning gates, backups with point-in-time recovery. Done when a timed restore drill completes inside the agreed RTO.

Two invariants make this a platform rather than a collection of servers: Terraform modules are versioned and shared, so a fix lands once and propagates, and CI enforces the conventions — plan on pull request, policy checks, secret scanning, naming and label rules, and scheduled drift detection. Configuration authored outside version control — in platform UIs, hand-edited files, or manually created firewall rules — diverges from the repository over time, which is what the scheduled drift check is there to surface.

Summary

  • Hetzner’s permission model is a project-scoped API token, so one project per environment is the isolation boundary the design has to use.
  • Port 22 stays closed to the internet. Access arrives through a plane that issues short-lived credentials against an identity provider.
  • The observability contract — OTLP plus structured logs to a local collector — is the decision that keeps the backend choice reversible, which makes it more consequential than the backend itself.
  • Postgres operations move in-house with the migration. Continuous WAL archiving, an immutable off-provider copy and a timed quarterly restore drill are the working baseline.
  • Build bottom-up, and stop at a layer boundary where it makes sense. Each layer is complete on its own.

For the price comparison behind the decision, see Hetzner vs AWS: The Real Cost Difference in 2026. For this platform delivered as a package rather than assembled by hand, see the Hetzner landing zone.

Planning a move to Hetzner?

Book a 30-minute call with Pratik — we will map your current estate against these seven layers and tell you what is already covered and what is still to scope.

Book an intro call

Related Posts

Bedrock Agents vs AgentCore: What to Use Now

Bedrock Agents vs AgentCore: What to Use Now

Updated 2 September 2026: Amazon Bedrock Agents Classic moved to maintenance mode in June 2026. This post has been rewritten around the options that are actually available now. **Amazon Bedrock Age

Read more
Connect Claude Code to Live AWS Tools with the Agent Toolkit

Connect Claude Code to Live AWS Tools with the Agent Toolkit

AI coding agents are getting remarkably capable — but they have a blind spot. The models powering them were trained on data that's months or years old. When you ask your agent about Amazon S3 Tables,

Read more
AWS Bedrock vs SageMaker: How to Pick the Right One

AWS Bedrock vs SageMaker: How to Pick the Right One

If you're building an AI product on AWS, you'll hit this question early: Bedrock or SageMaker? The short answer is that they solve different problems, and most startups only need one. What Each Se

Read more
Cheaper Alternatives to AWS in 2026: What Each One Cuts

Cheaper Alternatives to AWS in 2026: What Each One Cuts

There is no single cheapest alternative to AWS, because AWS bills are not shaped the same way. A bill dominated by EC2 has a different answer from one dominated by egress, RDS or GPU-hours. And severa

Read more
Deploying Engineering Resource Management Knowledge Graph on AWS

Deploying Engineering Resource Management Knowledge Graph on AWS

Resource planning in engineering orgs is a multi-hop problem. The data is there — skills, project history, availability — it's just stored in flat tables that you need to join on demand. This post wal

Read more
Hetzner vs AWS: The Real Cost Difference in 2026

Hetzner vs AWS: The Real Cost Difference in 2026

For a standing 8-vCPU, 16 GB server, AWS charges about $212 per month on demand and Hetzner charges €20.99, or roughly $25. That is a factor of nine, and it is not a rounding error. The gap is also no

Read more
RAG, GraphRAG, and Knowledge Graphs: What's Actually Different

RAG, GraphRAG, and Knowledge Graphs: What's Actually Different

LLMs are stateless. They don't know your documents, your internal data, or what changed last week. They're only as good as what you put in front of them. This gave rise to what's now called context en

Read more
LLM Inference on AWS: Every Option Explained

LLM Inference on AWS: Every Option Explained

AWS gives you two fundamentally different ways to run an LLM -SageMaker, you provision and pay for the infrastructure that serves the model. Bedrock, AWS already runs the model, and you just call

Read more
How to Build RAG on Your S3 Documents with Bedrock

How to Build RAG on Your S3 Documents with Bedrock

If your documents already sit in Amazon S3 and you want to ask questions of them, the shortest path on AWS is Amazon Bedrock Managed Knowledge Base: point it at the bucket, and it handles parsing,

Read more
S3 Vectors vs OpenSearch Serverless for RAG on AWS

S3 Vectors vs OpenSearch Serverless for RAG on AWS

This is a choice you only face on the customer-managed path — if you have not ruled out the fully managed option yet, [building RAG on your S3 documents with Bedrock](/blog/rag-on-s3-documents-with-be

Read more
What Is a Knowledge Graph?

What Is a Knowledge Graph?

A knowledge graph stores information as entities and the relationships between them — not rows and columns, but a web of connected facts. The Idea Is Simple Three building blocks:Nodes —

Read more
What Is Amazon Bedrock AgentCore? (And When to Use It)

What Is Amazon Bedrock AgentCore? (And When to Use It)

Amazon Bedrock AgentCore is a managed platform for deploying and operating AI agents you've already built — in any framework, with any model — without managing the runtime, memory, identity, or observ

Read more