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

  1. Point the client to the HTTPS endpoint (https://host) and method-appropriate API paths instead of the HTTP redirect port
  2. Change the request to GET/HEAD if it is only meant to test reachability of the redirect
  3. 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

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.

Related errors


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)