---
title: "Configuration — Safe Fetch for Laravel | Roundly"
description: "Every key of config/safe-fetch.php — http, ports, host and range lists, the app-host block, redirects, the fetch cap and budget, the resolver."
url: https://roundly-consulting.com/open-source/docs/safe-fetch-for-laravel/configuration
language: en
---

[All packages](https://roundly-consulting.com/open-source.md)

[Safe Fetch for Laravel](https://roundly-consulting.com/open-source/docs/safe-fetch-for-laravel.md)

# Configuration

config/safe-fetch.php is the default policy behind Http::safe(), SafeFetch::vet(), SafeFetch::fetch() and the safe\_url rule. The blocked-network table always applies; these settings only add to it or carve out of it. Publish it with --tag=safe-fetch-config:

```php
use RoundlyConsulting\SafeFetch\Support\SystemHostResolver;

return [

    // http too (local development, internal tools). https only by default.
    'allow_http' => env('SAFE_FETCH_ALLOW_HTTP', false),

    // null => any port 1–65535; otherwise the only ports a URL may use, e.g. [443].
    'ports' => null,

    'hosts' => [
        // Non-empty => ONLY these hosts (exact or *.example.com). Still resolved,
        // judged and pinned: an allowed host never makes a private address OK.
        'allowed' => [],

        // Refused by name, before any lookup.
        'blocked' => [],

        // Names whose addresses may be private (e.g. minio.internal). Still
        // resolved and pinned.
        'private' => [],
    ],

    'ranges' => [
        // Addresses / CIDRs exempt from the blocked-network table, e.g.
        // ['127.0.0.1/32', '10.20.0.0/16']. 0.0.0.0/0 and ::/0 are refused.
        'allowed' => [],

        // Extra addresses / CIDRs always refused; they beat every allow.
        'blocked' => [],
    ],

    // The app's own host names (app.url's host + extra) are refused by name, and
    // app.url's address when it is written as one: a request to yourself through
    // a public load balancer is SSRF too.
    'app_hosts' => [
        'block' => true,
        'extra' => [],
    ],

    'redirects' => [
        // Off by default: a redirect is returned to the caller unfollowed.
        'enabled' => env('SAFE_FETCH_REDIRECTS', false),

        // Hops followed at most, 0–10.
        'max' => env('SAFE_FETCH_MAX_REDIRECTS', 3),

        // null => 307 and 308 (method and body kept); SafeFetch::fetch(), which
        // only sends GETs, follows 301, 302, 303, 307 and 308 then. Otherwise a
        // subset of those five.
        'statuses' => null,

        // https -> http.
        'allow_downgrade' => false,

        // Header names also sent to another origin a redirect leads to
        // (Accept, Content-Type, User-Agent and the like always are).
        'forward_headers' => [],
    ],

    'fetch' => [
        // SafeFetch::fetch() body cap in bytes, at least 1.
        'max_bytes' => env('SAFE_FETCH_MAX_BYTES', 5_242_880),

        // SafeFetch::fetch() time budget in seconds, 1–600, every hop included.
        'timeout' => env('SAFE_FETCH_TIMEOUT', 30),
    ],

    // The resolver behind every lookup: any class implementing
    // RoundlyConsulting\SafeFetch\Contracts\HostResolver.
    'resolver' => SystemHostResolver::class,

];
```

## Every key

| Key | Env | Default | Purpose |
| --- | --- | --- | --- |
| `allow_http` | `SAFE_FETCH_ALLOW_HTTP` | false | http as well as https. |
| `ports` | — | null | null for any port, or a non-empty list of 1–65535 — the only ports a URL may use. |
| `hosts.allowed` | — | \[\] | Host patterns; non-empty means only these hosts. Still resolved, judged and pinned. |
| `hosts.blocked` | — | \[\] | Host patterns refused by name, before any lookup. |
| `hosts.private` | — | \[\] | Host patterns whose addresses may be private (minio.internal) — never a cloud-metadata address. |
| `ranges.allowed` | — | \[\] | Addresses or CIDRs exempt from the blocked-network table; never 0.0.0.0/0, ::/0 or an IPv6 prefix carrying all of IPv4. |
| `ranges.blocked` | — | \[\] | Addresses or CIDRs always refused; they beat every allow. |
| `app_hosts.block` | — | true | Refuse the app’s own host names, and app.url’s address when it is written as one. |
| `app_hosts.extra` | — | \[\] | Host patterns that also mean the app — the host of app.url always does. |
| `redirects.enabled` | `SAFE_FETCH_REDIRECTS` | false | Follow redirects; off returns a redirect to the caller unfollowed. |
| `redirects.max` | `SAFE_FETCH_MAX_REDIRECTS` | 3 | Hops followed at most, 0–10. |
| `redirects.statuses` | — | null | null: 307 and 308 (all five for fetch() and download()); otherwise a subset of 301, 302, 303, 307, 308. |
| `redirects.allow_downgrade` | — | false | Allow https → http. |
| `redirects.forward_headers` | — | \[\] | Header names also sent to another origin a redirect leads to. |
| `fetch.max_bytes` | `SAFE_FETCH_MAX_BYTES` | 5242880 | At least 1 — the body cap of fetch() and download(). |
| `fetch.timeout` | `SAFE_FETCH_TIMEOUT` | 30 | 1–600 seconds — the budget of fetch() and download(), every hop included. |
| `resolver` | — | SystemHostResolver::class | Any instantiable class implementing Contracts\\HostResolver; anything else throws when resolved. |

## Strict reading

- Every key is read strictly: a wrong type or a value out of range throws InvalidConfigurationException naming the key, instead of falling back to something looser.
- Blank (a host’s KEY=) means not set and takes the default.
- Env booleans accept true/false, 1/0, on/off and yes/no. Lists take an array or an env comma list.
- Settings that would switch the protection off entirely are refused — 0.0.0.0/0 and ::/0 in ranges.allowed, or an IPv6 prefix carrying all of IPv4 (::ffff:0:0/96, 2002::/16).
- There is no global on/off switch. Code that needs one keeps its own setting and simply does not call the guard.

## Environment variables

```bash
SAFE_FETCH_ALLOW_HTTP=false
SAFE_FETCH_REDIRECTS=false
SAFE_FETCH_MAX_REDIRECTS=3
SAFE_FETCH_MAX_BYTES=5242880
SAFE_FETCH_TIMEOUT=30
```

## The app’s own hosts

The host of app.url always counts as the app — read as https when it has no scheme (myapp.example, myapp.example:8080). An app.url written as an address (https://52.10.20.30) is refused as app\_host too, in any written form. Another public name for the same IP is not recognised: list it in app\_hosts.extra.

## Translations

resources/lang/{en,sk}/messages.php holds one line per reason (status takes :status) and validation.php the safe\_url message (with :attribute). Publish them with --tag=safe-fetch-translations to reword them.

## Show your open-source love

This package is free and MIT-licensed. If it saves you time, a one-off donation or a Patreon membership keeps it maintained, tested and documented.

[More ways to support, including crypto](https://roundly-consulting.com/support-us.md)

By donating, you agree to our [donation terms](https://roundly-consulting.com/donation-terms.md).

[Support our open source work (opens in a new tab)](https://donate.stripe.com/dRmeVe8FX5PF1Qd9pXcEw00) [Join us on Patreon (opens in a new tab)](https://www.patreon.com/cw/roundly)

## Want this built into your product?

We integrate our packages into custom Laravel and AI builds. Tell us what you're working on and we'll reply within 48 hours.

[Get a quote in 48 hours](https://roundly-consulting.com/contact.md) [Browse all packages](https://roundly-consulting.com/open-source/docs/safe-fetch-for-laravel.md)
