semaphoreui/semaphore · error
http requests forbidden
Error message
http requests forbidden
What it means
This is not a thrown panic but an HTTP 403 response from the TLS HTTP-redirect handler: any request whose method is not GET, HEAD, or OPTIONS is rejected with 'http requests forbidden' because only navigation requests should ever hit the insecure redirect listener.
Solutions
- Point the client to the HTTPS endpoint (https://host) and method-appropriate API paths instead of the HTTP redirect port
- Change the request to GET/HEAD if it is only meant to test reachability of the redirect
- Remove the HTTP redirect listener entirely if the plain-HTTP port should not be exposed
Example fix
// before curl -X POST http://semaphore.example/api/external/... // after curl -X POST https://semaphore.example/api/external/...
Defensive patterns
Strategy: validation
Validate before calling
const url = new URL(endpoint)
if (url.protocol !== "https:" && ["POST","PUT","DELETE","PATCH"].includes(method)) {
throw new Error("non-GET requests must use the HTTPS endpoint")
} Prevention
- Point API clients and webhooks at https:// URLs, never the HTTP redirect port
- Use GET/HEAD only when probing the redirect listener
- Update integrations after enabling TLS
- Document that the plain-HTTP port is redirect-only
When it happens
Trigger: Sending POST/PUT/DELETE/PATCH (or any non-GET/HEAD/OPTIONS method) to the plain-HTTP redirect port while TLS is enabled — e.g. an API client or webhook posting to http://host:80 instead of https://host:443.
Common situations: CI jobs or integrations hardcoded to http:// that POST to the redirect port; health probes or webhooks pointing at the wrong port/scheme; browsers/clients not upgraded after enabling TLS.
Understand the failure class
Background: "API error: {status}" and "HTTP 401/403/404/429/5xx" errors: non-2xx HTTP responses explained — this error's family across 27 libraries.
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- Unauthorized
- internal error
- Account linking must be initiated with a POST request.
- You must be signed in to link an external account.
- OIDC sign-in failed: state cookie is missing. Try signing…
AI-assisted analysis of semaphoreui/semaphore@1774ccb71a (2026-09-07).
Data as JSON: /api/errors/9a3edab736e838d9.
Report an issue: GitHub.
Appendix: source
Thrown at cli/cmd/root.go:385
if util.Config.WebHost != "" {
webHost, err2 := url.Parse(util.Config.WebHost)
if err2 != nil {
log.Panic(err2)
}
target += webHost.Host + r.URL.Path
} else {
hostParts := strings.Split(r.Host, ":")
host := hostParts[0]
target += host + port + r.URL.Path
}
if len(r.URL.RawQuery) > 0 {
target += "?" + r.URL.RawQuery
}
if r.Method != "GET" && r.Method != "HEAD" && r.Method != "OPTIONS" {
http.Error(w, "http requests forbidden", http.StatusForbidden)
return
}
http.Redirect(w, r, target, http.StatusTemporaryRedirect)
}))
if err != nil {
log.Panic(err)
}
}()
}
err = http.ListenAndServeTLS(util.Config.Interface+port, util.Config.TLS.CertFile, util.Config.TLS.KeyFile, cropTrailingSlashMiddleware(router))
if err != nil {
log.Panic(err)
}
} else {View on GitHub (pinned to 1774ccb71a)