To avoid server side request forgery (SSRF) in Go, never fetch a user supplied URL freely. Allow list destinations where you can, validate the IP address at connection time rather than only the hostname, control redirects, and keep the service's network access narrow.
What is SSRF?
MITRE's CWE-918 describes SSRF as a web server receiving a URL or similar request from an upstream component and retrieving its contents without making sure the request goes to the expected destination. The server becomes a proxy that an outsider can steer, often toward internal systems that outsiders cannot reach directly.
OWASP's SSRF prevention cheat sheet lists common triggers: an avatar image fetched from a user supplied address, a custom webhook, and internal calls between services that include user data. It also notes that SSRF is not limited to HTTP.
Which case are you in?
The OWASP guidance splits the problem in two, and this is the best starting point.
Case 1: your application only needs to talk to known services. Then use an allow list. A webhook that always goes to your payment partner needs one fixed host, not a free form URL. This is the strongest defence because the attacker has nothing to steer.
Case 2: your application must fetch arbitrary external addresses. A link preview or an import by URL feature falls here. An allow list is not possible, so you have to block internal destinations and constrain the request in several layers.
What does a safe Go client do?
Go's standard library gives you the pieces. Concepts, not a full listing:
- Parse and check the URL. Use
net/urlto parse. Accept onlyhttps(andhttpif you must). Reject user info in the URL and unexpected ports. - Check the address that is actually dialled. A hostname can resolve to an internal address, or change its answer between your check and your connection. In
net.Dialer, theControlfunction is called after the network connection is created but before dialling, and it receives the resolved address. Checking there means you validate the real destination, not the name. - Block internal ranges. The
net.IPtype offersIsLoopback,IsLinkLocalUnicastandIsPrivate. Note the Go documentation warns thatIsPrivatedoes not describe a security property of addresses, so treat it as one input to your own deny logic, not a complete filter. Also deny unspecified and multicast addresses, and the cloud metadata address range, which is link-local. - Control redirects. The
http.ClientfieldCheckRedirectis called before each redirect is followed. Use it to re-run your checks on the new target, or to refuse redirects entirely. - Set timeouts and size limits. Use client timeouts and
io.LimitReaderso a request cannot hang or pull gigabytes. - Do not return raw responses. If the feature only needs an image's dimensions, return those, not the body. Raw output makes probing easy.
A constructed example
A Go service accepts a URL for a profile picture. The first version checks that the hostname does not contain the words "localhost" or "internal". An attacker supplies a hostname they control that resolves to a private address, and the service fetches an internal admin page. A fix that blocks only strings in the name does not help, because the problem is the address.
The revised version parses the URL, allows only https, dials through a Dialer whose Control function rejects loopback, link-local and private ranges, refuses redirects, and returns only the image if its content type is an image.
What outside the code helps?
OWASP lists network layer protections alongside application ones. Run the service in a network segment that cannot reach internal admin systems. Use egress rules that allow only needed outbound traffic. If you run on a cloud provider, require the more protected version of the instance metadata service, which the cheat sheet discusses for AWS, so a simple forged request cannot read credentials.
A deny list on its own is a last resort in the OWASP text. Attackers find forms of an address that filters miss.
How to check or apply this
- Search your Go code for
http.Get,http.Clientandnet.Dialcalls that take input from users or from stored configuration. - For each, decide whether Case 1 or Case 2 applies, and move Case 1 to a fixed allow list.
- Add a shared safe client package, so teams do not each write their own.
- Write tests that try loopback, private, link-local and redirecting destinations and expect refusal.
- Add egress controls at the network level.
- Include static analysis rules for unsafe outbound calls, and review findings by hand.
Quick check
Is validating the hostname enough?
No. A hostname can resolve to an internal address. Check the resolved IP at connect time.
Should I follow redirects?
Only if you need to, and then re-check every hop. Refusing redirects is the safest default.
Sources
- MITRE, CWE-918: Server-Side Request Forgery (SSRF)
- OWASP, Server Side Request Forgery Prevention Cheat Sheet
- Go documentation, package net
- Go documentation, package net/http
- OWASP API Security Top 10, 2023
Last reviewed: 18 October 2026.