Preparing a Cloudflare Zone for Public URLs¶
An App Server gets a public URL by being served through Cloudflare: anti-DDoS, WAF and cache in front, the origin IP hidden. The zone that carries those URLs needs a handful of settings Muppy cannot apply for you, because they are bought or switched on in the Cloudflare dashboard. This page lists them once, in the order they are applied.
This page is for whoever holds a Cloudflare zone in a Muppy instance. For the muppy.io
service the zone is muppy.cloud and Muppy owns it: a client of that service has nothing to do
here. With Muppy Enterprise Edition, a client runs Muppy servers under a zone of their own —
that client is the zone owner, and this page is theirs.
Two configurations¶
What a zone can serve turns on one paid add-on, Advanced Certificate Manager (ACM). Choose the configuration first: it decides the dashboard steps, the shape of the host names, and whether the zone can be shared between several Muppy instances.
| (a) A zone without ACM | (b) A zone with ACM | |
|---|---|---|
| Certificates | Universal SSL, which covers the apex and one label under it | Total TLS, one per proxied hostname, at any depth |
| Host names | flat, one label under the zone | one label per tenant, the server name under it |
| Their shape | partitioned by company, <server>-<company>-<host> |
the server name alone, or with its host, under the tenant's sub-zone |
| Who writes the records | this instance, straight onto the zone | each tenant's instance, through MBD |
| Sub-zones | none: the zone cannot host one | one per tenant, each an MBD Server |
| Customers' own names | yes | yes |
| Fits | one Muppy hosting servers for its own customers | several Muppy instances sharing one zone, one tenant each |
(a) is the simple case. One Muppy instance, one Cloudflare DNS Domain, records written straight onto the zone. Every name is one label under it, and the company slug inside the name is what keeps two customers' servers apart.
(b) shares one zone between instances. Each tenant gets a delegated sub-zone
<client>.<zone>, published as an MBD Server domain; the tenant's own
Muppy writes into it through an MBD Client, holding no Cloudflare credential. Names then sit two
labels under the zone — which is exactly what Universal SSL does not cover.
Why ACM decides this
Cloudflare's free Universal SSL certificate covers the zone apex and one level of
subdomain. A name such as app.acme.example.cloud sits two levels down and is served with
no valid certificate at all. Advanced Certificate Manager — 10 $ per month, per zone —
issues edge certificates at any depth and unlocks Total TLS, which issues one
automatically for every proxied hostname. Both are available on every plan, the Free plan
included; a payment method must be on the account. Total TLS issues a certificate about a
minute after a proxied record appears; until then the URL answers a TLS error, which the App
Server's system health check shows as a transient red state.
Muppy reads this from the zone. Advanced Certificate Manager (ACM) on the DNS Domain is set when the domain connects, and an MBD Server refuses a backend zone that does not carry it: names under the sub-zone would get no certificate.
The API token¶
Every Cloudflare DNS Domain in Muppy holds one token:
| Permission | Level | Used for |
|---|---|---|
| Zone | Read | resolving the zone id when the domain connects |
| DNS | Edit | the A, AAAA, CNAME and TXT records |
| SSL and Certificates | Read | reading Total TLS and the fallback origin when the domain connects, so Muppy sets the two switches itself |
| SSL and Certificates | Edit | declaring customers' own names, and reading their certificate status |
Edit includes Read. A token carrying neither still writes records: Muppy then leaves the two switches on the DNS Domain as you set them, and says so in the log.
Cloudflare offers two kinds of token. Both carry the same permissions; they differ in who owns them and in how the permission picker is laid out.
| Kind | Where | Owned by | Picker |
|---|---|---|---|
| User API token | My Profile → API Tokens → Create Token → Custom token | the person who creates it: it dies with their user account | a flat list: Zone → Zone, Zone → DNS, Zone → SSL and Certificates; scope under Zone Resources → Include → Specific zone |
| Account API token | Manage Account → Account API Tokens → Create Token | the Cloudflare account: it survives a person leaving, so use it for a production instance | grouped by product: DNS & Zones → Zone (Read) and DNS (Edit), Cache & Performance → Zone SSL & Certificates (Edit); the Search for permission groups box finds a row by name; scope under Specified Domains |
Name the token Muppy CF <zone> @ <instance>, for example
Muppy CF example.cloud @ h.example.cloud: the name says which zone it covers and which
instance holds it, which is what you look for in the token list the day one has to be revoked.
Restrict the token to the one zone. Add Client IP Address Filtering with the instance's public IP when it has a fixed one. The token goes into a Vault of type Cloudflare API Token on that instance, and nowhere else. Muppy reads the token's own expiry when the domain connects and shows it on the DNS Domain, so a dated token is visible before it expires.
A token with DNS Edit reaches every record of the zone. There is no way to scope one to a subdomain, and Muppy has no way to detach a record from a zone without deleting it at Cloudflare: treat the instance that holds the token as an operator of the whole zone.
The zone in the dashboard¶
These steps apply to both configurations:
| Step | Where in the dashboard | Setting |
|---|---|---|
| 1 | Domain Registration, or your registrar | the zone is registered, and its nameservers point at Cloudflare |
| 2 | Add a site | the zone is added in full setup: Cloudflare is its authoritative DNS. Status Active |
| 3 | Zone → Overview | the Free plan is enough |
| 4 | SSL/TLS → Overview → Configure | encryption mode Full. Flexible would reach the origin over plain HTTP; Full (strict) only once every origin serves a certificate Cloudflare can verify |
| 5 | SSL/TLS → Edge Certificates → Universal SSL | leave enabled: it serves the zone's first-level names |
| 6 | SSL/TLS → Edge Certificates | Always Use HTTPS on, Minimum TLS Version 1.2 |
| 7 | Security → Bots → Bot Fight Mode | leave off: it blocks API clients and health checks |
| 8 | Security → WAF → Managed rules | the free Cloudflare Managed Ruleset is the one available on the Free plan |
Configuration (b) adds two:
| Step | Where in the dashboard | Setting |
|---|---|---|
| 9 | SSL/TLS → Edge Certificates → Advanced Certificate Manager | Activate |
| 10 | SSL/TLS → Edge Certificates → Total TLS | choose a certificate authority, then enable |
Total TLS: never delete a certificate by hand
Deleting a Total TLS certificate from the dashboard excludes that hostname from every future issuance, even if its record is recreated. Records come and go with App Servers; certificates are Cloudflare's to manage.
Let's Encrypt as the Total TLS authority
Let's Encrypt limits new certificates to 50 per week per registered domain, and every proxied hostname of the zone counts. A zone that expects bursts of new App Servers is better served by Google Trust Services, which has no such published cap.
The zone in Muppy¶
Both configurations start the same way: one DNS Domain for the zone, provider Cloudflare DNS, with the vault above, then Connect. Connecting resolves the zone id, reads the token's expiry, and sets Advanced Certificate Manager (ACM) and Custom Hostnames (Cloudflare for SaaS) from the zone. Check Connection reads all of it again, so a feature switched on or off at Cloudflare is picked up on the next check. Both switches stay editable: tick them by hand when the token cannot read them.
(a) A zone without ACM. Tick Proxied by Default: every record this instance creates on the zone is then served through the edge, which is what gets it a certificate. App Servers take their public names from this domain, one label under the zone.
(b) A zone with ACM. Leave Proxied by Default unchecked: the proxy is asked per record
by each tenant's MBD Client, and a zone-level default would proxy records tenants did not ask
for. Then one MBD Server domain per tenant, named <client>.<zone>, its Backend DNS Domain
set to the zone, with the tenant's SigV4 token. On each tenant's instance, an MBD Client
domain of the same name, with Proxied by Default (via MBD Server) ticked.
The Backend DNS Domain picker offers only domains that can host a delegated sub-zone, so a Cloudflare zone appears there once its ACM switch is on. The rest — records, TTL, certificates — follows from the App Server configuration described in Network Configuration.
Sync the CloudflareIPsV4 and CloudflareIPsV6 Dynamic CIDR Ranges once on every
instance that runs a Traefik host for public applications, and attach them to that host: its
firewall then admits Cloudflare's edge only on port 443. The ranges are fetched by the range's
Sync button, never at module update. Attach the same two ranges to the Traefik
server's Trusted forwarding proxies as well if any application on it must filter its own
visitors rather than the edge; see
Network Configuration.
Custom hostnames (customers' own domains)¶
A customer who wants erp.example.com in front of one of the zone's public names is served by
Cloudflare for SaaS: they create one CNAME to that public name, Cloudflare issues an edge
certificate for their own name and connects to the proxied record behind it. The customer needs
no Cloudflare account and no Cloudflare-hosted zone — a plain CNAME at any DNS provider is
enough. One hundred custom hostnames are included per zone, then 0,10 $ per hostname per month.
Their side of it is described in Custom Domains.
Enable the feature once on the zone:
| Step | Where in the dashboard | Setting |
|---|---|---|
| 1 | SSL/TLS → Custom Hostnames | Enable Cloudflare for SaaS |
| 2 | DNS → Records | an A record fallback, proxied, pointing at any origin that answers on 443 |
| 3 | SSL/TLS → Custom Hostnames → Fallback Origin | fallback.<zone>, then Add Fallback Origin |
The fallback origin is required by Cloudflare and never reached in practice: every custom
hostname carries its own custom origin server, the proxied record behind it. Wildcard custom
hostnames (*.example.com) are an Enterprise feature and are not supported.
Once both are done, the DNS Domain's Custom Hostnames (Cloudflare for SaaS) switch goes on at the next Check Connection, and the Custom Hostnames page appears on the domain.
Declaring a custom hostname¶
Muppy declares them. Fill Custom Domain (CNAME) on the Traefik Application carrying the name and press Declare; the state travels back and is shown there. Nobody declares anything by hand in the dashboard.
In configuration (a) the instance declares straight onto the zone. In (b) the request travels the path a DNS record travels — the tenant's MBD Client, this MBD Server, this zone — so the operator holds no per-tenant credential, and two settings gate it on the MBD Server domain for that tenant:
| Setting | Meaning |
|---|---|
| Custom Hostnames Quota | How many of its own names that tenant may declare. It defaults to 0, which forbids them entirely — each one is an edge certificate this zone is billed for, so it is granted per tenant rather than by default. A name still waiting for its certificate holds a slot. |
| The backend | The MBD Server offers the feature only if its backend zone does. A tenant whose backend cannot serve custom hostnames is refused with a message naming it. |
An MBD Server also refuses any origin that is not an existing, proxied record of the subdomain that tenant's token delegates. The custom name is deliberately not checked: it lives outside any delegation, and only Cloudflare's own validation proves the tenant controls it.
Raising the quota is not enough on its own
Cloudflare for SaaS must be enabled on the zone and the fallback origin set, as described above, before any declaration can succeed. The DNS Domain shows this as its Custom Hostnames (Cloudflare for SaaS) switch, and a declaration made without it is refused before it reaches Cloudflare.
Names declared by hand are adopted, not duplicated. When Muppy is asked to declare a name that already exists on the zone, it takes the existing custom hostname over — same id, same certificate, no interruption. This is how a zone that was managed manually comes under Muppy's management without any name going down.
Limits are Cloudflare's, and are not repeated here. There is a ceiling on how fast certificates may be issued, and a declaration whose validation never succeeds is eventually abandoned. Both are documented by Cloudflare and change without us: troubleshooting and limits, backoff schedule. Muppy reports what it is told: a name it is polling stays Pending, and one the provider gave up on comes back as an error to be declared again.
Declaring one by hand¶
Should you need to, in the dashboard (SSL/TLS → Custom Hostnames → Add Custom Hostname) or
through the API (POST zones/<zone id>/custom_hostnames with the hostname,
ssl: {method: http, type: dv} and custom_origin_server). Three settings matter:
| Setting | Value | Why |
|---|---|---|
| Custom hostname | the tenant's name, erp.example.com |
the name Cloudflare will answer to and issue a certificate for |
| Custom origin server | the tenant's proxied public name, erp-prod.acme.<zone> |
where the edge connects for this name. It changes the connection only: the request keeps the tenant's Host header, so the Traefik application must carry the name in its router rule, which is what Custom Domain on the Traefik Application does |
| Certificate | HTTP validation, the zone's default authority | validation is served at the edge once the CNAME resolves through Cloudflare; the origin is never asked. The authority is the one selected for the zone's custom hostnames, SSL.com by default, not the Total TLS one |
The hostname reports two states. status turns active as soon as the CNAME is seen, before
any certificate exists; ssl.status turns active when the certificate is issued, about two
minutes after the declaration in our measurements, and that is the moment the name starts
answering. Read ssl.status, not status, to know that a name is ready — which is what Muppy's
own State is computed from; see Cloudflare's
validation status
for the full set of values. Deleting the custom hostname deletes its certificate with it.
What the origin receives¶
Measured on the pilot with a listener inside the App Server, on both the tenant's public name and a
custom hostname: the two requests have exactly the same shape, only the names and the edge
address differ. Cloudflare resolves the custom origin server internally and connects to the
origin once (Cdn-Loop: cloudflare; loops=1); the request does not re-enter the edge.
| Header | Value seen by the application | Meaning |
|---|---|---|
Host, X-Forwarded-Host |
the name the visitor typed | the custom hostname when there is one, so Odoo's proxy_mode sees the tenant's name |
CF-Connecting-IP |
the visitor's address | the visitor address, always |
X-Forwarded-For |
<visitor>, <edge> |
the visitor first, then Cloudflare's edge added by Traefik |
X-Real-Ip |
the Cloudflare edge address | not the visitor: an application that trusts this header attributes every visit to Cloudflare |
X-Forwarded-Proto, Cf-Visitor |
https |
the visitor's scheme |
Read the visitor from CF-Connecting-IP or from the first entry of X-Forwarded-For, never
from X-Real-Ip. This holds for every application served through the zone, custom hostname or
not. An Odoo application also rewrites web.base.url from the name an administrator signs in
with unless web.base.url.freeze is set: with two names in front of one instance, freeze it.