In Blog #27, we protected state-changing browser requests from CSRF. Now we focus on a different server-side risk: SSRF. Server-Side Request Forgery happens when an attacker influences a request made by your server and causes it to connect to an unintended network destination.
The browser is not making the protected outbound request—the server is. That server may have network access, credentials, bandwidth, or trust relationships that the original caller does not. Next.js SSRF prevention therefore starts by removing unnecessary destination choice, then layers application policy, resource limits, and network controls around the outbound request.
What Is SSRF in Next.js?
Server-Side Request Forgery occurs when attacker-controlled input influences a request made by the Next.js server, causing it to access an unintended destination such as an internal service, loopback interface, private network, or other sensitive endpoint. Prevention requires strict URL parsing, destination restrictions, redirect controls, resource limits, and safe network architecture.
SSRF at a Glance
Every outbound feature has two questions: who controls the data, and who controls the destination? A city name sent to one configured weather provider is ordinary input. A complete URL submitted to a generic proxy gives the caller far more control. The safest design reduces that control before trying to validate every possible address representation.
const target = request.nextUrl.searchParams.get('url')
const response = await fetch(target!)The problem is not merely malformed input. A perfectly parseable URL can still name a destination the application must never reach. Content filtering after the request is too late because the connection has already crossed the security boundary.
Prefer Identifiers to Arbitrary URLs
When the product supports a known set of providers, accept an identifier and map it to a destination controlled by server code or a server-only environment variable. Users choose the business operation, not the network origin. This is easier to test, easier to audit, and dramatically smaller than a general URL-validation problem.
const providers = {
weather: new URL('https://weather.example'),
exchange: new URL('https://rates.example'),
} as const
type Provider = keyof typeof providers
export function providerUrl(provider: Provider, path: string) {
return new URL(path, providers[provider])
}Common SSRF Entry Points
Review any feature that obtains a remote resource on behalf of a user: external API routes, image or avatar importers, Open Graph previews, document downloads, screenshot or PDF services, import-from-URL tools, webhook delivery, callback checkers, redirect validators, and generic proxy routes. Not every feature is vulnerable; risk appears when untrusted input can influence the destination beyond the intended policy.
Direct URL features
Link previews, media fetchers, imports, screenshots, and callbacks often begin with a complete URL and therefore need the strongest review.
Composed destinations
A fixed origin with user-controlled paths or queries is safer, but path joining, redirects, credentials, and forwarded headers still require deliberate handling.
Route Handlers and External API Proxies
Blog #10 explains that Route Handlers are public HTTP endpoints. A handler that accepts { url } and returns fetch(url) creates a generic network capability. Prefer a business-specific route whose upstream origin is fixed in server configuration and whose query values are validated.
const WEATHER_ORIGIN = new URL(process.env.WEATHER_API_ORIGIN!)
export async function GET(request: Request) {
const city = new URL(request.url).searchParams.get('city')?.trim()
if (!city || city.length > 80) {
return Response.json({ error: 'Invalid city.' }, { status: 400 })
}
const upstream = new URL('/v1/forecast', WEATHER_ORIGIN)
upstream.searchParams.set('city', city)
const response = await fetch(upstream, {
redirect: 'error',
signal: AbortSignal.timeout(5_000),
})
return Response.json(await response.json())
}Keep the trusted origin in a server-only variable as described in Blog #16: Environment Variables & Security. Never place a private service origin in a NEXT_PUBLIC_ variable merely to reuse it in a client component.
Server Actions and Authentication
A Server Action can create SSRF just as a Route Handler can. 'use server' changes where code executes; it does not certify a submitted URL. Treat every form field as untrusted, authenticate and authorize sensitive integration changes, and apply the same destination policy before any connection.
Blog #13 covers authentication and resource authorization. Those controls restrict who may configure an integration, but a logged-in user can still submit a dangerous destination. URL validation remains mandatory.
Safe URL Parsing and Normalization
Use the standard URL parser. Inspect protocol, normalized hostname, port, username, password, and—when the business rule needs it—pathname and search. Do not make raw regular expressions or startsWith() the primary URL security boundary.
const allowedHosts = new Set(['weather.example', 'rates.example'])
export function validateTrustedUrl(input: string) {
const url = new URL(input)
if (url.protocol !== 'https:') throw new Error('Protocol rejected')
if (url.username || url.password) throw new Error('Credentials rejected')
if (url.port !== '') throw new Error('Custom port rejected')
if (!allowedHosts.has(url.hostname)) throw new Error('Host rejected')
return url
}The URL API normalizes details such as hostname casing and internationalized domain representation. Still compare the parsed component to the intended policy. A URL may contain username/password-style userinfo before the hostname, so visually scanning the whole string is not reliable. Avoid hostname.includes('example.com'); exact equality is clearer. If subdomains are truly required, use a reviewed dot-boundary rule and document who can create those subdomains.
Allowlist vs Denylist
OWASP separates the easier case—requests can reach only identified trusted applications—from the harder case where a product must fetch arbitrary public destinations. Use an allowlist whenever business requirements permit it. Exact server-owned origins eliminate large classes of normalization, DNS, redirect, and network ambiguity.
A denylist alone is fragile because the public Internet and address syntax change, one hostname can resolve to multiple addresses, IPv4 and IPv6 coexist, and a redirect can select a second destination. If arbitrary public URLs are unavoidable, use a maintained IP-address parser, resolve all address records, reject every non-public result, bind the validated resolution to the actual connection where your HTTP stack supports it, and reinforce the rule at the network layer.
Protocol, Hostname, Port, and Userinfo
Most public integrations should allow only https:. Reject file, data, FTP, and any other scheme the feature does not explicitly need. If a trusted internal architecture genuinely requires another protocol, isolate it behind a purpose-built server module rather than expanding a public generic fetcher.
Compare the normalized hostname exactly. Restrict the port: for standard HTTPS, an empty parsed port is usually the cleanest policy. User-controlled custom ports can expose services that were never designed as HTTP APIs. Reject URL userinfo so credentials are not confused with hostname text or copied into logs and outbound requests.
Private Destinations, IPv4, IPv6, and DNS
If the product must accept arbitrary public URLs, parsing the hostname is only the beginning. A hostname can resolve to loopback, private, link-local, reserved, multicast, or other non-public space. Resolve both A and AAAA records, classify every result with a maintained, tested IP library, reject the request if any answer violates policy, and define how the connection uses the validated result.
A simple “resolve, check, then fetch the hostname normally” sequence can create a time-of-check/time-of-use gap because the HTTP client may resolve again. DNS answers can also change. Robust designs use a reviewed HTTP agent or egress gateway that connects only to the checked address while retaining correct TLS hostname verification, or move arbitrary fetching to a tightly isolated managed service. Do not invent this transport layer from a short blog snippet.
Redirect Handling and Open Redirects
Do not validate only the first URL and then follow redirects blindly. The new location is a new destination. Disable redirects when the integration does not require them. Otherwise set a small maximum, require an absolute or correctly resolved location, parse and validate the destination again, re-run DNS/IP checks, and only then make the next request.
async function fetchTrusted(start: URL) {
let current = validateTrustedUrl(start.href)
for (let hop = 0; hop <= 2; hop++) {
const response = await fetch(current, {
redirect: 'manual',
signal: AbortSignal.timeout(5_000),
})
if (response.status < 300 || response.status >= 400) return response
const location = response.headers.get('location')
if (!location || hop === 2) throw new Error('Redirect rejected')
current = validateTrustedUrl(new URL(location, current).href)
}
throw new Error('Redirect rejected')
}The Proxy and redirects guide in Blog #17 covers browser-facing redirects. SSRF concerns the server HTTP client following an upstream redirect; the same open-redirect endpoint can become part of both problems, but the trust boundaries are different.

Timeouts, Response Limits, and Content Types
A destination can be public and still consume resources. Set an outbound deadline with AbortSignal.timeout() or the approved client mechanism. Limit concurrency and method choice. Avoid automatic retries for attacker-influenced requests: retries multiply load and prolong slow operations.
Check Content-Length when present, but do not trust it as the only limit. Stream the body and stop when the measured byte count exceeds the feature budget. Validate content type before decoding and handle mismatches as errors. A filename extension does not prove the body is an image or document. Do not buffer an unbounded response with arrayBuffer(), text(), or json() first and attempt to check its size afterward.
Image and Media Fetching
Next.js Image remotePatterns can restrict protocol, hostname, port, path, and search values for the built-in optimizer. Define all components narrowly. Current Next.js 16 image documentation also exposes redirect and response-body limits and keeps local-IP optimization disabled by default. Review those options against the installed version and deployment.
remotePatterns is not a universal SSRF defense. It does not govern your custom fetch(), external avatar importer, screenshot service, PDF renderer, webhook delivery, or custom image loader. The official documentation also notes that remote image redirects do not need to match remotePatterns again, so reduce or disable image redirects when appropriate.
Safe Link Previews
Link preview and unfurl features are high-risk because arbitrary public URLs are often the product requirement. Consider an isolated fetch service with restricted egress rather than running preview requests inside the primary application network. Accept only HTTP(S) URLs required by the feature, reject userinfo and non-public destinations, validate DNS results and each redirect, use a short timeout, read only a small capped amount, and parse only necessary metadata.
Do not execute scripts, forward the user's cookies, attach application authorization headers, or reuse a browser session. Sanitize displayed metadata, cache by a normalized bounded key, and avoid globally caching private upstream content.
Incoming and Outgoing Webhooks
Incoming webhooks and outgoing webhooks point in opposite directions. For incoming events, verify the provider signature over the required raw body, enforce freshness or replay controls, and make processing idempotent. For outgoing webhooks, a customer-configured destination creates an SSRF boundary: require HTTPS, restrict ports, validate DNS and IP addresses, revalidate redirects or disable them, limit time and bytes, rate-limit delivery, and sign your payload so the recipient can authenticate it.
Docker and Internal Services
The production stack in Blog #20, Blog #21, and Blog #25 includes private application networks. Database, Redis, and worker services should not be publicly exposed, yet the application container may legitimately reach them. An SSRF-capable HTTP client must not turn that reachability into user-controlled access.
Separate public ingress, application, data, and outbound paths. Expose only the required service interfaces, keep databases and caches off public networks, apply least-privilege network rules, and do not return internal service names or resolved addresses in client errors.

Network-Level Defenses and Cloud Environments
Application validation is only one layer. Outbound firewall rules, a controlled egress proxy, service isolation, cloud security groups, platform firewall policy, and a dedicated fetch service can prevent the application from reaching destinations it never needs. These controls also help when a parser, dependency, or future route contains a mistake.
Cloud and VPS environments may expose sensitive host-local or provider services that ordinary Internet clients cannot reach. Do not allow user-controlled outbound requests to access non-public endpoints. Use the platform's current hardened metadata mode and network controls where available, but never treat a provider feature as permission to keep an unrestricted application fetcher.
Response and Error Handling
Do not reflect arbitrary upstream headers into the client. Forward only data and headers the business feature requires; unrestricted forwarding can leak cookies, create cache conflicts, or override security policy. Return a neutral client error such as { "error": "Unable to fetch the requested resource." }. Keep internal hostnames, resolved addresses, stack traces, and infrastructure details out of public responses.
Server logs should record a stable event name, reason category, route, safe account or request reference, duration, and release identifier. Avoid secret query strings, authorization headers, tokens, credentials, and full sensitive URLs. If a hostname is necessary for security analysis, normalize it and apply the organization's data-handling policy.
Monitoring, Rate Limiting, and Caching
Useful events include rejected protocol, blocked destination category, redirect rejection, timeout, oversized response, content-type mismatch, and repeated suspicious requests. Aggregate without high-cardinality secrets. Connect those events to the safe logging and alerting model in Blog #24: Monitoring & Observability.
Expensive outbound endpoints should usually be rate-limited, as described in Blog #22: Redis Rate Limiting. Rate limiting controls volume, not destination safety. For caching, normalize and bound keys, separate tenants and authorization contexts, and never cache private upstream content globally.
Common Next.js SSRF Mistakes
- Calling
fetch(userInput). - Building an arbitrary-URL proxy.
- Using raw string-prefix validation.
- Using
hostname.includes(). - Depending only on a denylist.
- Validating a hostname but not DNS results.
- Checking IPv4 while ignoring IPv6.
- Following redirects after validating only the first URL.
- Allowing arbitrary ports or protocols.
- Accepting URL userinfo.
- Omitting timeout and concurrency controls.
- Buffering an unlimited response.
- Trusting file extensions instead of content type.
- Assuming authentication prevents SSRF.
- Assuming
remotePatternsprotects every fetcher. - Allowing user-directed access to private containers.
- Logging complete blocked URLs with secret queries.
- Forwarding all upstream response headers.
- Using regular expressions as the only URL parser.
- Adding retries to attacker-controlled work.
- Shipping without network egress controls.
Audit the Existing Application for SSRF
| Location | Input source | Outbound request | User-controlled destination? | Risk / action |
|---|---|---|---|---|
layer.php contact form | Browser form fields | Same-origin POST to /send-mail.php | No | Not a server-side fetch; keep endpoint fixed. |
send-mail.php | Server configuration + form message | SMTP to a fixed provider host | No | No arbitrary destination found; protect credentials and logs. |
| Next.js Route Handlers | Not present | None in runtime repository | N/A | Audit the real Next.js app before applying examples. |
| Server Actions / proxy | Not present | None in runtime repository | N/A | No installed Next.js application or version detected. |
| Media, preview, webhook delivery | Not present | No runtime fetcher found | N/A | Keep absent unless a scoped business need is defined. |
Scope note: Article code blocks that demonstrate fetch(), URLs, Docker service names, or webhooks are publishing content and were not classified as runtime network calls. No claim is made about a separate, unseen Next.js codebase.
Testing SSRF Defenses
Build a local, authorized test matrix from policy categories rather than publishing a payload collection. Confirm that each approved provider, path, content type, and redirect behavior works. Then verify rejection of disallowed schemes, credentials, unapproved hosts, unexpected ports, non-public IPv4 and IPv6 classifications, mixed DNS answers, prohibited redirects, slow responses, oversized bodies, wrong content types, and unauthorized configuration changes.
Test through the same DNS resolver, HTTP agent, proxy, container network, and egress rules used in production-like environments. Confirm that rejections happen before a prohibited connection, public errors stay generic, logs remain redacted, limits release resources, and rate controls behave consistently across replicas. Security tests should be run only against systems you own or are authorized to assess.
Next.js 16 SSRF Prevention Checklist
- Avoid arbitrary URLs where possible.
- Map identifiers to fixed trusted origins.
- Parse with the standard
URLAPI. - Allow only required protocols.
- Use exact hostname allowlists.
- Restrict ports and reject userinfo.
- Normalize before policy and cache decisions.
- Review internationalized hostname handling.
- Classify IPv4 and IPv6 addresses.
- Validate every resolved DNS answer.
- Bind validation to the actual connection.
- Disable or revalidate redirects.
- Set a small redirect maximum.
- Configure a short outbound timeout.
- Limit response bytes while streaming.
- Validate content type before parsing.
- Limit concurrency and request methods.
- Do not retry attacker-directed work blindly.
- Keep credentials out of URLs.
- Do not forward user cookies or auth headers.
- Avoid generic proxy endpoints.
- Authorize integration configuration.
- Sign outgoing webhook payloads.
- Rate-limit expensive fetch endpoints.
- Keep Docker services private.
- Apply network egress restrictions.
- Return neutral public errors.
- Redact sensitive URL components in logs.
- Monitor repeated rejection categories.
- Follow current OWASP and framework guidance.
Frequently Asked Questions
What is SSRF in Next.js?
Server-Side Request Forgery occurs when untrusted input influences a request made by the Next.js server and causes it to connect to an unintended destination. Prevention combines narrow destination policy, correct URL parsing, redirect and DNS controls, resource limits, and network isolation.
Can fetch() cause SSRF?
Yes, when attacker-controlled input determines all or part of the destination without an effective policy. The fetch API is not itself a vulnerability; the unsafe trust boundary around the destination creates the risk.
Are Server Actions vulnerable to SSRF?
They can be if a Server Action fetches a destination influenced by submitted form data or another untrusted value. The use server directive does not make a URL trustworthy.
Are Route Handlers vulnerable to SSRF?
They can be. Route Handlers are public HTTP endpoints, so a handler that accepts an arbitrary URL and fetches it needs a strong business justification and layered destination controls.
How do I validate URLs in Next.js?
Parse with the standard URL API, then apply an explicit policy to the normalized protocol, hostname, port, credentials, path, DNS results, and every redirect destination. Parsing alone is not authorization to fetch.
Should I use an allowlist or denylist?
Prefer a small exact allowlist when the business requirement permits it. A denylist is harder to keep complete because address forms, DNS answers, redirects, IPv6, and infrastructure change.
Why is startsWith() unsafe for URL validation?
A raw string prefix is not a reliable URL security boundary. Parse the value and compare normalized URL components instead of trusting how the complete string looks.
Should I block private IP addresses?
When arbitrary public URLs must be fetched, reject non-public destinations according to a reviewed IPv4 and IPv6 policy and validate all resolved addresses. Network egress controls should reinforce the application check.
What about IPv6?
IPv6 must be classified as carefully as IPv4. A policy that checks only IPv4 is incomplete and can allow an unintended local, private, reserved, or otherwise non-public destination.
Can redirects cause SSRF?
Yes. A URL that initially passes policy can redirect elsewhere. Disable redirects where possible, or limit them and parse, resolve, and validate every new destination before making the next request.
Does authentication prevent SSRF?
No. Authentication identifies the caller, and authorization can limit who configures integrations, but neither proves that a supplied network destination is safe.
Does Next.js Image remotePatterns prevent all SSRF?
No. remotePatterns narrows sources handled by the built-in image optimizer, but it is not a universal policy for custom fetch calls, link previews, webhooks, proxies, or other HTTP clients. Redirect and image-loader settings also need review.
Can Docker internal services be exposed through SSRF?
Potentially, if the application can reach private services and user-controlled destination logic permits those connections. Keep internal services private, restrict egress, and do not expose their addresses through application errors.
Are webhooks related to SSRF?
Outgoing webhooks create SSRF risk when users can configure their destinations. Incoming webhooks primarily require provider authentication such as signature and replay verification; these are different directions and trust boundaries.
What network controls help prevent SSRF?
Egress firewall policy, a restricted outbound proxy, network segmentation, service isolation, and provider security controls can provide defense in depth. They do not remove the need for application validation.
Should I build a generic /api/proxy endpoint?
Usually not. Prefer a scoped endpoint with a server-configured upstream and validated business parameters. A generic arbitrary-URL proxy adds SSRF, bandwidth, caching, and content-handling risk.
How should an outbound request be limited?
Set a short timeout, cap redirects and response bytes, constrain methods and headers, validate content type, limit concurrency, and rate-limit expensive public endpoints. Avoid automatic retries for attacker-controlled destinations.
Current Official References
- OWASP SSRF Prevention Cheat Sheet
- Next.js Data Security guide
- Next.js Route Handlers guide
- Next.js Backend for Frontend guide
- Next.js
use serverreference - Next.js Image configuration
- Node.js WHATWG URL API
- Node.js DNS API
- AbortSignal timeout reference
Next Steps
Inventory the real application's outbound requests first. Replace arbitrary destinations with fixed origins, then apply parsing, allowlists, DNS/IP controls, redirect revalidation, time and byte limits, safe logging, and egress policy only where the architecture needs them. This reduces flexibility at exactly the boundary where flexibility becomes risk.
