Adding an IP Firewall to Next.js Without Cloudflare

On Vercel, Edge middleware blocks at the edge. On self-host, the SecureNow preload blocks at the HTTP server. Both work without DNS changes.

Lhoussine
May 9, 2026·6 min read

Adding an IP Firewall to Next.js Without Cloudflare

If you don't want to put Cloudflare in front of your Next.js app — for routing reasons, vendor preference, or just simplicity — there are two clean alternatives. Pick based on where your app runs.

For broader context, see the SecureNow Firewall page.

On self-hosted Next.js (Railway, Render, AWS, your own server)

The simplest setup: preload the firewall before Next.js starts.

npm install securenow

# Update your start script:
node -r securenow/firewall-only node_modules/next/dist/bin/next start

Or via NODE_OPTIONS in your Dockerfile / process manager:

NODE_OPTIONS='-r securenow/firewall-only' npm start

Result: every request to your Next.js server passes through an IP check. Blocked IPs get a 403 before Next.js even sees the request. Allowlist for legitimate crawlers (Googlebot, GPTBot, etc.) is automatic.

The Pages Router and App Router are both protected — the firewall sits at the HTTP server level, below the framework.

On Vercel (Edge middleware)

Vercel Edge functions can't use the Node-runtime preload. Use Edge middleware:

// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
import { kv } from '@vercel/kv';

const STATIC_BLOCKED = new Set([
  '185.220.101.42',
  // ... a small static list
]);

const GOOD_BOTS = /googlebot|bingbot|gptbot|claudebot|perplexitybot/i;

export async function middleware(request: NextRequest) {
  const ua = request.headers.get('user-agent') || '';
  if (GOOD_BOTS.test(ua)) return NextResponse.next();

  const ip = request.headers.get('x-forwarded-for')?.split(',')[0]?.trim() || '';
  if (STATIC_BLOCKED.has(ip)) {
    return new NextResponse('Forbidden', { status: 403 });
  }

  const dynamicBlock = await kv.sismember('blocklist:ips', ip);
  if (dynamicBlock) {
    return new NextResponse('Forbidden', { status: 403 });
  }

  return NextResponse.next();
}

export const config = {
  matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'],
};

Populate blocklist:ips from a cron job that pulls a threat-intel feed:

// app/api/cron/refresh-blocklist/route.ts
export async function GET(req: Request) {
  // Verify cron secret
  const auth = req.headers.get('authorization');
  if (auth !== `Bearer ${process.env.CRON_SECRET}`) {
    return new Response('Unauthorized', { status: 401 });
  }

  const ips = await fetchAbuseipdbBlocklist(); // your fetch logic
  await kv.del('blocklist:ips');
  await kv.sadd('blocklist:ips', ...ips);

  return new Response(`Refreshed ${ips.length} IPs`);
}

Then schedule it in vercel.json:

{
  "crons": [{ "path": "/api/cron/refresh-blocklist", "schedule": "0 * * * *" }]
}

That gets you hourly refreshes. Caveats: Vercel KV pricing applies (free tier covers small-to-mid traffic), and you have to maintain the threat feed fetch logic.

On Vercel + SecureNow (hybrid)

For Node-runtime portions of your Next.js app on Vercel (Route Handlers configured for Node, server components on Node), you can run SecureNow there:

// instrumentation.ts
export async function register() {
  if (process.env.NEXT_RUNTIME === 'nodejs') {
    await import('securenow/register');
  }
}

This gives you the firewall + tracing on Node-runtime requests. Edge-runtime requests fall through to whatever middleware you have. The combination covers both.

Verifying it works

Self-host:

npx securenow firewall status
# Shows blocklist size and last refresh

Vercel Edge:

curl -i https://yourapp.com/api/something -H "X-Forwarded-For: 185.220.101.42"
# Expect: HTTP/1.1 403 Forbidden

Cost comparison

ApproachSetup timeMaintenanceMonthly cost
Self-host + securenow/firewall-only1 minute0$0
Vercel + Edge middleware (static list)30 minuteslow$0
Vercel + Edge middleware (KV-backed)1-2 hoursmedium~$5–$30 (KV)
Vercel + Cloudflare in front1-2 hourslowvaries

For self-hosted Next.js, the SecureNow preload is the obvious choice. For Vercel deployments, the Edge middleware + KV approach works but is more maintenance — Cloudflare in front is sometimes worth the DNS change.

Related

Frequently Asked Questions

Does the SecureNow firewall work on Vercel?

The Node-runtime preload doesn't work on Vercel Edge functions. For Vercel deployments, use Edge middleware for IP blocking and `instrumentation.ts` to enable SecureNow on the Node-runtime portions of your app.

Edge middleware lets you block based on a hard-coded list or one fetched from KV. The SecureNow firewall provides a 500k-IP managed blocklist refreshed hourly with auto-allowlisting for legitimate crawlers. Less work to maintain.

Will this protect API routes only, or pages too?

Both. The HTTP-server-level block applies to every request before Next.js routes it. Edge middleware applies according to your matcher config.

Is there a performance impact?

Sub-millisecond. The IP lookup is in-memory hash + CIDR range scan. For 500k entries, cold start adds ~50MB memory and per-request overhead is negligible.

Recommended reading

Adding Backend Tracing to a Sentry Stack with OpenTelemetry

If your team uses Sentry for frontend errors and needs backend distributed tracing without doubling the Sentry bill, here's the OpenTelemetry path that doesn't make you choose.

May 9
How to Block Bot Traffic in Express With No Extra Infra

Five approaches to bot blocking in Express, ranked by effort vs. effectiveness. From a 5-line allowlist to a full IP-reputation firewall — all without Cloudflare, AWS WAF, or any new infrastructure.

May 9
How to Block Bot Traffic in Fastify With No Extra Infra

Fastify hooks (onRequest) and the SecureNow preload both work cleanly. Here's the production setup for IP blocking and user-agent filtering.

May 9