ThisCalculator

Everyday

Multi-Host Routing in Go: One Binary, Many Domains

Updated · Standard formula, shown below

Average pace 5.00 min/km
Average speed
12.00 km/h
Result
Finish time
50 min
You entered
Distance
10 km
You entered

Multi-host routing in Go lets a single binary serve requests for multiple domain names, each with its own handler logic, by matching the incoming request’s Host header against a configured set of rules. You get this by using the standard library’s http.ServeMux (Go 1.22+) or a third-party router, and it’s a common pattern for API gateways, white-label SaaS, and shared infrastructure. This article explains how it works, how to implement it, and what pitfalls to avoid.

Formula

The calculator above is a pace calculator, not a routing calculator—but the formula concept is the same: you take two inputs and produce a derived output. For pace, the formula is:

pace (min/km) = finish time (min) / distance (km)

speed (km/h) = distance (km) / (finish time (min) / 60)

Where finish time is in minutes, distance is in kilometers, pace is minutes per kilometer, and speed is kilometers per hour. The calculator uses these exact formulas to convert your inputs.

Worked Example

Using the defaults: finish time = 50 minutes, distance = 10 km.

  1. Pace = 50 / 10 = 5.00 min/km
  2. Speed = 10 / (50 / 60) = 10 / 0.8333 = 12.00 km/h

The calculator returns pace 5.00 min/km and speed 12.00 km/h, matching the formulas.

Why Multi-Host Routing Matters

When you run a Go web server, the default http.ServeMux routes based on the request path only. If you have two domains—say api.example.com and admin.example.com—both will hit the same handler unless you explicitly inspect the Host header. Multi-host routing solves this by dispatching requests to different handlers based on the domain, allowing one binary to serve completely separate APIs, dashboards, or static sites.

This is especially useful in microservice architectures where a single edge service routes to internal services, or for hosting multiple client sites from one deployment. It reduces infrastructure overhead and simplifies deployment because you only manage one process.

How It Works in Go

In Go, every incoming HTTP request contains a Host field (or you can read it from r.Host). The standard library’s http.ServeMux since Go 1.22 supports patterns that include a host. For example:

mux := http.NewServeMux()
mux.HandleFunc("api.example.com/", apiHandler)
mux.HandleFunc("admin.example.com/", adminHandler)

This is the simplest approach: the mux matches the host part of the pattern against the request’s Host header. If you need more complex logic—like wildcard subdomains, path-based fallbacks, or dynamic routing—you can write a custom middleware that inspects r.Host and dispatches accordingly.

For wildcard subdomains (e.g., *.example.com), the standard mux does not support wildcard hosts, so you’d need a custom router or a middleware that strips the subdomain and routes based on it. Third-party routers like chi or gorilla/mux also allow host-based routing, but the standard library is often sufficient for simple cases.

Choosing Between Standard Library and Third-Party Routers

The decision depends on your needs:

  • Standard library http.ServeMux: Best for simple host-based routing, especially with Go 1.22+ patterns. It’s zero-dependency, fast, and covers most cases.
  • Third-party routers (chi, gorilla/mux): Offer more features like regex patterns, middleware chaining, and route groups. Use them when you need complex routing rules or host extraction with variables.
  • Custom middleware: When you need to route based on host plus path, or when you want to add logging or auth before routing, a middleware that wraps the mux gives you full control.

For a single binary serving many domains with distinct handlers, the standard mux is usually enough. If you need to extract the subdomain as a variable (e.g., tenant1.example.com), you’ll likely write a small custom handler that parses r.Host and then calls the appropriate sub-handler.

Common Pitfalls and Misconceptions

One common mistake is forgetting that the Host header can be spoofed or may include a port number (e.g., example.com:8080). Always strip the port before matching. Another pitfall is assuming that the mux will match the longest host pattern—it actually matches the most specific pattern, which is the one with the longest matching host and path. If you have both example.com and api.example.com, requests to api.example.com will match the latter, but requests to example.com will not match the former if the pattern is example.com/—it will only match the root path.

A frequent misconception is that you need a reverse proxy (like Nginx) to do multi-host routing. While proxies are common, Go can handle it directly, saving a layer of infrastructure. However, if you need TLS termination or load balancing, a proxy is still useful.

Cost and Performance Considerations

Multi-host routing in Go adds negligible overhead—it’s just a string comparison on the Host header. The main cost is in your code structure: if you have many domains, you need to manage a map of host-to-handler, which is memory-efficient. For high-traffic services, the standard mux is optimized and performs well.

One performance consideration is that using a custom middleware that does heavy processing on every request (like DNS lookups) can slow things down. Keep routing logic simple and avoid blocking calls. Also, if you serve many domains, ensure your TLS certificate covers all of them (e.g., using a wildcard or SAN certificate), otherwise you’ll need SNI-based routing, which Go’s http.Server supports via tls.Config.GetCertificate.

Alternatives to Multi-Host Routing in Go

If you don’t want to handle routing in your Go binary, alternatives include:

  • Reverse proxy (Nginx, Caddy, Traefik): These can route based on Host header to different backend services. This is useful if you have services in different languages or need advanced load balancing.
  • API Gateway (Kong, Envoy): More feature-rich, with authentication, rate limiting, and analytics. Overkill for simple multi-domain serving but powerful for large microservice architectures.
  • Separate binaries per domain: Simplest to reason about but increases deployment complexity and resource usage.

Each approach has trade-offs. In-process routing is the lightest, while a proxy adds a network hop but provides more flexibility.

Safety and Security Considerations

When routing based on Host header, be aware of HTTP Host header attacks. An attacker can send a request with a Host header that doesn’t match any of your domains, and your server might respond with a default handler or an error. Always have a fallback handler for unknown hosts—either return a 404 or redirect to a canonical domain. Also, validate that the Host header is in your expected list before processing sensitive actions.

For TLS, if you use automatic certificates (e.g., Let’s Encrypt via a proxy), ensure the host routing matches the certificate names. In Go, you can implement GetCertificate to return the correct certificate based on the SNI name, which is derived from the Host header. This is safe as long as you validate the host against your allowed list.

Frequently Asked Questions

Can I use wildcard subdomains with the standard mux? No, the standard http.ServeMux does not support wildcard hosts. You’ll need a custom middleware that extracts the subdomain and routes based on it, or use a third-party router that supports host patterns with wildcards.

How do I handle the port number in the Host header? Use net.SplitHostPort if the host includes a port, or simply strip everything after the colon. For example, strings.Split(r.Host, ":")[0] gives you the hostname without the port.

What happens if a request comes with an unknown Host? By default, Go’s mux will match the root pattern if you have one, or return a 404. It’s best practice to explicitly handle unknown hosts with a dedicated handler that returns a 404 or a redirect to your main domain.

Can I serve HTTPS for multiple domains with one Go server? Yes, use tls.Config.GetCertificate to select the certificate based on the SNI name. You’ll need a certificate that covers all domains (wildcard or SAN).

Is it better to route in Go or with a reverse proxy? It depends. For a single binary with simple routing, Go is simpler and faster. For multiple services or advanced features like rate limiting, a proxy like Nginx or Traefik is more appropriate.

Does multi-host routing affect performance? Negligibly. The host match is a string comparison. The bigger performance factor is the handler logic you run after routing.

Related

Formula used
in t | Finish time | min | 50 | 0
in d | Distance | km | 10 | 0
out pace | Average pace | min/km | t / d | 2
out speed | Average speed | km/h | d / (t / 60) | 2

Each line is one input (in, sel, list) or one result (out: label, unit, formula, decimals). How the calculators work

Method: the standard formula for this calculation, rounded as each result states. See how the calculators work or report an error.