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.

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. There's no built-in crawler allowlist; add any crawler IPs you rely on as trusted IPs.

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 fetchSecureNowIpdbBlocklist(); // 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 (npx securenow init writes it and updates next.config)
export async function register() {
  if (process.env.NEXT_RUNTIME === 'nodejs') {
    try {
      const securenowNext = await import('securenow/nextjs');
      const registerSecureNow = securenowNext.registerSecureNow || securenowNext.default?.registerSecureNow;
      registerSecureNow({ captureBody: true });
      await import('securenow/nextjs-auto-capture');
    } catch (error) {
      console.error('[securenow] SecureNow did not start; the app is running without it:', error);
    }
  }
}

On Vercel this sends traces from Node-runtime requests through @vercel/otel, with the credentials in a SECURENOW_CREDENTIALS secret. That path isn't tested end to end yet, and the firewall hasn't been verified on Vercel serverless functions, so keep the Edge middleware above for blocking. Edge-runtime requests fall through to whatever middleware you have.

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$99 per app (after a 14-day free trial)
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.

Frequently Asked Questions

Does the SecureNow firewall work on Vercel?

The firewall runs inside a Node.js process, so it doesn't run on Vercel Edge functions, and it hasn't been verified on Vercel serverless functions. For Vercel deployments, use Edge middleware for IP blocking; `instrumentation.ts` can send traces from the Node-runtime portions of your app (not yet tested end to end on Vercel).

What's the difference vs Edge middleware?

Edge middleware lets you block based on a hard-coded list or one fetched from KV. The SecureNow firewall provides a managed known-bad IP feed refreshed hourly, plus your own blocklist. 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. The feed is capped at 10,000 IPs, so memory use is small and per-request overhead is negligible.

Recommended reading

Block an IP, a Range or One Route Without a Deploy (Node.js)

Block one IP, a CIDR range, or a single route and method in a Node.js app within seconds, with no code change: setup, scoping, CLI, MCP, verification and undo.

Oct 27
11 Signs Your AI-Built App Needs a Security Audit

Built your app with a coding agent? 11 warning signs it needs a security audit, each checkable in five minutes, and how to run the full audit yourself.

Oct 26
AI Security Audit Part 1: Set Up Your Agent to Audit Code

Part 1 of the AI security audit series: set up an AI coding agent to audit your app locally, connect SecureNow, and run your first threat-model pass.

Jul 24