Security

What Hetscale can reach, what it writes and stores, and what it never touches.

Your traffic never passes through us

Hetscale is a control plane. It reads metrics and calls the Hetzner Cloud API in your project. Requests from your users go from your load balancer to your servers, as they do today. Hetscale is not in that path, cannot see it, and an outage on our side cannot interrupt it.

Tokens

A project you only observe holds a read-only Hetzner token. It cannot create, change or delete anything; that is enforced by Hetzner, not by us. Hetzner tokens come in exactly two levels, Read and Read & Write, and there is no upgrade from one to the other: going Live means you create a second token, which takes the place of the read-only one on that project.

Rehearsal, Propose and Live use a Read & Write token for the project. It is stored with envelope encryption, never placed in job payloads, and used only for the API calls the log shows. The master key that protects it lives in AWS KMS in Frankfurt and never leaves it: only Hetscale's control plane may use it, from its own address, and every use is logged, with an alert on any other. Authorization headers are scrubbed from logs; a token fingerprint detects the same token being connected to more than one organisation.

One token serves a whole project, so a group you still only observe in a project that has gone Live for another group reads through that Read & Write token. There it is our own write gate, not Hetzner, that holds every write for a group which is not Live.

Revoking the token in the Hetzner Console stops Hetscale from that moment; a Hetzner action already accepted may still complete. Nothing on your servers depends on us. What stays in your project and how to remove it, step by step: Leaving Hetscale.

Your pipeline talks to Hetscale with an API token you make on the API tokens page. Hetscale stores only its hash and shows it once. It acts for the member who made it, may do only what its abilities allow (report nodes, change releases, lock groups, read nodes, replace older servers, read metrics), expires after the time you choose, and stops at once when you revoke it or the member leaves.

Ownership guard

Hetscale deletes only servers it created and a seed server you promoted to a managed member, and only when their labels and name match its ledger. Anything else in the project is listed and never shut down or deleted, even when it sits behind the same load balancer. A node is never deleted while it is still a load balancer target. The same check covers the placement groups Hetscale creates to spread a group's servers: it deletes one only when it is empty and its labels and name match its ledger.

delete only when all three match the ledger:
  label   hetscale.dev/managed = true
  label   hetscale.dev/group   = <group ulid>
  label   hetscale.dev/node    = <node ulid>
  name    hetscale-<group prefix>-<number>
any mismatch → aborted, security alert, server listed, never shut down or deleted

During creation a fourth label, hetscale.dev/op, ties the server to the write-ahead intent that requested it. A server with Hetscale's labels and no matching intent is quarantined: Hetscale leaves it as it is and never adds it to the load balancer. Only when you confirm its deletion, and only when its labels and name are exactly those Hetscale gives its own servers, does Hetscale take it out of the load balancer and delete it; otherwise you delete it in the Hetzner Console. Every Hetzner call goes through one guarded client that never exposes rescue mode, password resets, console access, or deletion of volumes, networks, firewalls, load balancers or floating IPs. A server protected against deletion in Hetzner is a hard stop, never retried on its own. What Hetscale does with every other Hetzner answer, from a sold-out server type to a rate limit, is on Hetzner API errors and what Hetscale does.

What the guard does not do: it keeps Hetscale from shutting down or deleting a server it did not create, other than a seed you promoted. It does not restrain anyone who holds your Read & Write token and calls the Hetzner API directly; that token is project-wide, and Hetzner offers no narrower scope. The guard is a rule in our software; the token's reach is set by Hetzner. Give Hetscale a dedicated Hetzner project per application it manages, so the token reaches that project alone.

What Hetscale writes in your project

Hetscale makes these writes in your Hetzner project, and no others. With a read-only token Hetzner refuses every one of them.

WhatWhenDoes Hetscale remove it?
Servers named hetscale-<group prefix>-<number>, with the four hetscale.dev labels and none of yours. Each one boots a snapshot of your seed: with the snapshot adapter, the current release's, which Hetscale took or your pipeline named through the API; with the webhook adapter, release v1's, and your pipeline deploys the current release. A new server joins the seed's private networks; while spreading is on, a placement group of the node group; the seed's firewalls, when it has a public address; and the SSH keys you picked for new servers. On its first boot, one fixed cloud-init step removes Coolify's Sentinel container where there is one.Whenever the node group needs another server: the Rehearsal, a scale-out, healing, Replace older servers, or a server of a fallback type replaced once the group's own type is backYes, when the node group no longer needs it or when you choose Delete on the Servers page. It goes out of the load balancer, then is shut down and deleted after the three checks. A server that failed its start, or that healing replaced, is first kept for the time set in Keep a failed server. A server with these labels that Hetscale has no record of is quarantined and deleted only when you confirm it.
Those servers as targets of your load balancer, by private IP when the server has oneOnce a new server has booted; with the webhook adapter, once your pipeline reports it ready. The load balancer sends it traffic only after its health check passes.Yes, first, before the server is shut down
Snapshots of your seed, labelled for the node group; each one is a releaseOnly when you choose Take release v1 or Take a new releaseOnly when you confirm Delete unused snapshots, each after a check of its record, labels and description. Never release v1's, the current release's, the two newest other releases', one a running server booted from, or a snapshot Hetscale did not take
Placement groups named hetscale-<group prefix>-pg-<number>, labelled for the node group, up to ten servers eachWhen a new server needs one, while Spread servers across hosts is on (the default)Yes, once one is empty and no longer needed, after the same checks of record, labels and name
Delete protection on the servers and load balancers you confirm, with rebuild protection on the serversIn the Delete protection step on the Go Live page, before the first RehearsalNever: Hetscale does not turn protection off
Your seed renamed hetscale-<group prefix>-0, with the four labels added to its ownOnly when you choose Promote to managed, after you turned its delete protection off in the Hetzner ConsoleThe name and labels stay. From then on Hetscale may remove the server when load falls, as in the first row
One check that creates nothing: a request for an SSH key with an empty name and key, which Hetzner refusesWhen you connect a token or replace one, to tell Read from Read & WriteNothing is created

Nothing else. Hetscale never creates, changes or deletes volumes, networks, firewalls, floating IPs or SSH keys. A new server joins your seed's networks and firewalls, and gets the keys you picked, only as part of its own creation. Hetscale never creates or deletes a load balancer, and never changes its services or health check. It adds as targets only the servers it created and a seed you promoted, and it removes only those and a quarantined server you confirmed for deletion. It never rebuilds, rescues, resizes or reboots a server, and never resets its password. No setting, button or API call skips the three checks.

Each new server gets the same kinds of public address as your seed: an IPv4, an IPv6, both or none. Hetzner counts each one toward your account's Primary IP limit. Servers without public addresses has the details.

Delete protection

Before the first Rehearsal, Hetscale lists the servers and load balancers of the project that it did not create, with the seed, the other servers behind the load balancer and the load balancer itself checked, and turns Hetzner's delete protection on for what you confirm. It turns protection on, never off. Hetzner then locks rebuilds too. A server Terraform manages needs delete_protection = true in its configuration, or the next apply turns protection off again. To promote your seed later, you turn its protection off in the Hetzner Console yourself. Volumes, networks, firewalls, IPs and images are protected in the Console.

How we keep the control loop from doing the wrong thing

Every rule below is checked before any change to Hetscale is deployed, each by a scripted scenario or a test. A scenario is a timeline of load, Hetzner answers and minutes, played through the real control loop against a stand-in for Hetzner. It checks every decision Hetscale takes and every write it would send to Hetzner, in order.

  • Health before traffic. A new server gets traffic only after your load balancer’s health check passes; a ready report from your pipeline never opens it. A server that never passes is deleted and reported, and three in a row pause the node group, except while healing waits out a fault every server shares.
  • No scale-in without fresh metrics. Missing or stale metrics never remove a server.
  • A rate-limited answer is not a missing server. When Hetzner says too many requests were made, Hetscale backs off and decides nothing until it can read again. It never takes that answer to mean a server is gone.
  • The ownership guard’s three checks. Before a server is shut down or deleted, its record, labels and name must all match. Any mismatch stops the call and raises a security alert, and a server protected in Hetzner is a hard stop.
  • Your seed stays. It is never chosen for scale-in, never replaced by healing or a rollout, and never deleted: if it fails, a new server starts beside it. Only a seed you promote can be removed.
  • An intent before every create. Hetscale records each server before it asks Hetzner for it. When the answer to a create is lost, Hetscale finds the server and adopts it, so no server is created twice. A server with its labels that matches no intent is quarantined, not deleted.
  • Fallback stays close. A fallback to another location or server type stays in the load balancer’s network zone. It uses another type only in the fallback mode you picked: one of the types you listed, or the cheapest fitting type under the price cap you set.
  • Overload is not death. A server that fails its health check while its CPU is pinned is overloaded. The node group first adds capacity instead of replacing it, and when it cannot add any, nothing is deleted and you are told.

Hetscale's Hetzner client is tested against Hetzner's answers as its API specification documents them, and no test reaches Hetzner or any other outside service. When a live run shows a wrong action, a scenario that reproduces it is written before the fix, and it stays. Every change to the application also passes a formatting check, static analysis at its strictest level and the whole test suite with a coverage floor; a change that fails any of them is not deployed.

Hetscale has also run these setups against Hetzner, in its own Hetzner project and never in a customer's (dates in UTC):

  • 2026-09-26 Observe on a read-only token: the project read, the fit checked, the day-0 report written, and the decisions the live phase would have taken recorded.
  • 2026-09-27 The path to Live: a Read & Write token, delete protection on the seed and the load balancer, release v1 taken from an Ubuntu seed, and a Rehearsal whose server was routed, drained and deleted with the seed untouched. The same day, servers booted from releases of three sizes, each taking traffic only after the load balancer’s first healthy report.
  • 2026-09-28 On a copy of the production image: Hetscale’s workers killed in the middle of a create and of a boot, its database and queue restarted, and its network cut with a create in flight. Each case ended as an undisturbed run would, with no server created twice and no drain or delete repeated.
  • 2026-10-01 Healing with a minimum of one server: the failed seed got one replacement from the current release and was kept, and once it recovered, the replacement left by scale-in. A replacement from a broken release never passed its health check, was deleted, and healing stopped with an alert.
  • 2026-10-02 The snapshot cleanup: unused snapshots were deleted, while release v1’s, the current release’s, the two newest others’ and a snapshot Hetscale did not take stayed in place. One whose label had been changed outside Hetscale was refused with a security alert, and one under delete protection was left in place.
  • 2026-10-03 Placement groups created as servers arrived and deleted through the checks once empty; and servers created without public addresses, routed through their private IPs behind the project’s own NAT server.
  • 2026-10-06 On a copy of the production image: metrics switched off on purpose, and no scale-in while they were missing or stale. A server deleted outside Hetscale was noticed as gone and replaced, and Hetscale sent no delete for it.
  • 2026-10-09 A seed that Coolify 4.4.3 deploys, cloned onto every new server with Coolify’s Sentinel removed, from the Rehearsal through Live to a rollout of a second release.

None of this is an audit or a certification.

Outgoing webhooks

With the webhook adapter and with a webhook integration, Hetscale calls the https URL you set. Every delivery is signed with a secret that Hetscale keeps sealed and shows you once; after a rotation the old secret keeps signing for a day. A Slack or Discord URL is kept sealed like the secrets and shown masked, its host and last four characters. Hetscale resolves the host itself and connects only to public, globally routable addresses, never to a private network, follows no redirect and gives up after 10 seconds.

Signing in

Dashboard passwords need at least 8 characters and are checked against known breaches.

You can also sign in with GitHub or Google. Hetscale asks them only for your profile and email address, accepts only an address they have verified and keeps no GitHub or Google token. If someone else started an account with your address and never verified it, your first sign-in there takes it over: their password stops working and their sessions end.

You can add an authenticator app on your profile, with recovery codes; it is never required. Its secret is stored encrypted and the recovery codes only as hashes. An account with an authenticator app signs in with its email, password and code only: a sign-in with GitHub or Google is refused, so it never skips the code.

Others join your organisation only through an invitation to their address. Its link works once and for seven days, Hetscale stores only its hash, and it opens nothing for an account with another address.

What we never store

  • SSH private keys. Hetscale does not log in to your servers.
  • Tokens in job payloads or logs.
  • Request payloads or customer traffic; none of it passes through us.
  • Application secrets. Your deploy tool owns them; the one disclosed exception is the seed’s snapshot, which new nodes boot from with the snapshot and webhook adapters alike: secrets on the seed’s disk live inside that snapshot, in your own project. Every later release’s snapshot carries them too; the Deployment page deletes the snapshots no release or server needs.

Data processing

Metrics, decisions and the action log are stored in the EU, on Hetzner servers in Germany. We run Hetscale from Türkiye. Every organisation has an audit log: each change its people make, on the panel or with an API token, and each write Hetscale makes to its Hetzner projects, with Hetzner's action ids. Within a minute every entry is sealed into the organisation's SHA-256 hash chain; the database refuses to change an entry or to delete one younger than a year, and every chain is checked daily. An export lets you check the chain yourself, so the log is tamper-evident from your export on. Operator access to your tokens or your provider data outside the automated paths is planned: requested with a reason, usable after a 30-minute delay, mailed to your organisation's admins when requested and written to the audit log, with a second approval once a second operator exists.

The data processing agreement describes the data we process for your organisation, how long we keep it and the sub-processors. It is part of the terms and applies to every organisation.

Who runs Hetscale

Hetscale is run by its founder as a sole proprietorship registered in Türkiye; the terms name the business. The founder runs it from Türkiye and is today the only person with access to your data. The application and its database run in a Hetzner data centre in Germany. Mail to support@hetscale.com reaches the founder directly.

Your node groups do not wait for anyone at Hetscale: the control loop scales and heals on its own. If Hetscale itself stops, your traffic does not, because it never passes through Hetscale and nothing on your servers depends on it. Your load balancer and servers keep serving as they are, and nothing scales or heals until Hetscale is back. When it is back, it reads the current state of your project and continues from there: a server it had already started to remove finishes leaving, and nothing else is deleted because it was away. To stop Hetscale yourself, revoke its token in the Hetzner Console. Leaving Hetscale lists what stays in your project and how to remove it.

Reporting a vulnerability

Write to support@hetscale.com. It is the only address for security reports, and it reaches the founder directly. The same address is in /.well-known/security.txt.

The hosts in scope are hetscale.com, app.hetscale.com and api.hetscale.com. A useful report names:

  • The host and the page, URL or API route.
  • The steps to reproduce it, with the requests and answers that show it.
  • What someone could read or change through it.
  • The account or organisation you tested with, and when, in UTC.

While you test, use only your own account, organisation and Hetzner project. If you reach another customer's data, stop, keep none of it, and tell us what you saw. Do not run load, flooding or denial-of-service tests. Please give us time to fix a problem before you make it public. There is no paid bug bounty.

See what Hetscale would have done with your real data — connect read-only, get your report in minutes.

Connect read-only