A merchant storefront can live on a platform subdomain (mystore.platform.example) or a custom domain (www.store.example). Both options are served by the same Go application. Here is how we resolve which shop to render.
The Shop Host Middleware
When a request hits the shops binary, a middleware extracts the Host header and figures out which shop it belongs to:
func (m *ShopHostMiddleware) Resolve() gin.HandlerFunc {
return func(c *gin.Context) {
host := requestHost(c.Request)
if host == "" || m.isApexHost(host) {
c.Next()
return
}
if slug, ok := m.subdomainSlug(host); ok {
if _, isCountry := helper.Countries[slug]; isCountry {
c.AbortWithStatus(http.StatusNotFound)
return
}
}
shop, err := m.resolveShop(c.Request.Context(), host)
if err != nil {
c.AbortWithStatus(http.StatusInternalServerError)
return
}
if shop == nil {
if m.isShopSubdomain(host) {
c.AbortWithStatus(http.StatusNotFound)
return
}
c.Next() // not a configured custom hostname
return
}
c.Set(ShopContextKey, shop)
c.Next()
}
}
resolveShop tries two strategies:
-
Subdomain lookup: extracts
mystorefrommystore.platform.exampleand looks up that unique slug. -
Custom-domain lookup: normalizes the supported
www.store.examplehostname to the stored root,store.example, and looks it up through a unique index.
Country subdomains are explicitly excluded because they belong to the marketplace application. We parse hostnames with net.SplitHostPort, lowercase them, and validate them against known suffixes. If a reverse proxy supplies X-Forwarded-Host, trust it only from a proxy you control.
Cloudflare for SaaS: The SSL Part
The hardest part of custom domains is not routing but certificate issuance and renewal. We use Cloudflare for SaaS custom hostnames for that lifecycle.
The setup:
- We configure a fallback origin such as
connect.platform.example - When a merchant adds a custom domain, our app calls the Cloudflare API to create a Custom Hostname
- The merchant sets
CNAME www → connect.platform.exampleat their DNS provider
Our Custom Hostname request uses HTTP DCV. Once the www CNAME routes through Cloudflare, Cloudflare can validate the hostname and issue the certificate for www.store.example. Cloudflare retries failed validation on a backoff schedule. A status GET only observes that process; if we need an immediate recheck after DNS is fixed, the documented API flow is to PATCH the hostname with its current SSL settings.
Why www-Only
We only support www.{domain}, not bare apex domains. A conventional CNAME cannot coexist with the other records required at a DNS zone apex. Some DNS providers offer ALIAS/ANAME or CNAME flattening, but support and behavior vary. The merchant therefore configures an HTTP redirect from store.example to www.store.example with their DNS or registrar provider.
This is a product constraint, not a universal Cloudflare limitation. Cloudflare for SaaS also documents apex proxying, but its prerequisites and plan availability should be checked before committing to it.
Status Tracking
For each shop we store:
- the provider's opaque Custom Hostname ID
- a local status:
pending,active, orerror - a provider status or error summary
When a merchant connects a domain, we create or find the Cloudflare hostname, store its ID, and map Cloudflare's hostname and SSL states to our smaller status set. The dashboard can explicitly refresh that state. A scheduled poller is another option, but the current implementation does not depend on one.
The API token stays server-side and is never returned to the browser; production credentials should have only the required zone permissions. Provider error text is untrusted. Our templates HTML-escape it, but a stronger boundary is to log detailed provider errors server-side and show merchants a bounded, generic message.
What We Learned
-
Define one canonical hostname. In our case, input may include
www., but storage uses the root and serving useswww.. This rule must be consistent in validation, provisioning, and lookup. -
Use reserved example domains in documentation. A wildcard under a local
.testdomain is enough to exercise host routing without Cloudflare. - Index both lookup keys. Host resolution is on the request path, so subdomain slugs and custom domains need unique indexes. Add caching only with an explicit invalidation strategy.
- Make provisioning idempotent. Before creating a hostname, look it up; retries should not produce duplicate provider resources.
Custom domains are table stakes for many SaaS storefront products. Cloudflare for SaaS made it possible without running our own ACME client or certificate renewal infrastructure.
This article is based on lessons from building Towami, a multi-country marketplace and white-label storefront platform. Follow for more practical notes on Go, HTMX, infrastructure, and SaaS.
Top comments (0)