Skip to main content
Back to blog
March 9, 2026|7 min read|Hardening Guides

Permissions-Policy Header: The Complete Configuration Guide

Learn how the Permissions-Policy HTTP header works, which browser features to restrict, and how to configure it on Nginx, Apache, Cloudflare, and Next.js.

ZeriFlow Team

1,044 words

What Is Permissions-Policy?

The Permissions-Policy HTTP header gives you control over which browser features and APIs your website can use. Think of it as a whitelist for browser capabilities — if you don't need the camera, microphone, or geolocation APIs, you can explicitly disable them.

When set correctly, Permissions-Policy prevents:

  • Malicious scripts from accessing sensitive device APIs (camera, microphone)
  • Embedded iframes (ads, widgets) from using features you didn't authorize
  • Feature abuse by compromised third-party libraries

Here's a basic example:

Permissions-Policy: camera=(), microphone=(), geolocation=()

This tells the browser: "This page and all embedded content are not allowed to use the camera, microphone, or geolocation." The empty parentheses () mean "no origins are allowed."

Why It Replaced Feature-Policy

The Permissions-Policy header is the successor to the older Feature-Policy header. The change happened for several reasons:

  • New syntax — Feature-Policy used a different syntax (Feature-Policy: camera 'none'), while Permissions-Policy uses structured headers (Permissions-Policy: camera=())
  • Broader scope — Permissions-Policy covers more features and APIs
  • Better integration — It works with the Permissions API in JavaScript, allowing sites to query their own permissions programmatically

Important: Some older browsers still only support Feature-Policy. For maximum compatibility, you can set both headers, but Permissions-Policy is the standard going forward.

nginx
# Nginx — both headers for compatibility
add_header Feature-Policy "camera 'none'; microphone 'none'; geolocation 'none'" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;

Permissions-Policy Syntax Explained

The syntax follows the structured headers specification:

Permissions-Policy: <feature>=<allowlist>

Allow Values

ValueMeaning
()Disabled for all origins
(self)Allowed for same origin only
*Allowed for all origins
("https://example.com")Allowed for specific origin
(self "https://example.com")Allowed for self and specific origin

Multiple Features

Separate features with commas:

Permissions-Policy: camera=(), microphone=(), geolocation=(self), payment=(self "https://stripe.com")

This policy: - Blocks camera and microphone entirely - Allows geolocation for same-origin only - Allows the Payment API for same-origin and Stripe's domain

Which Features to Restrict

Here's a comprehensive list of features you can control, with recommended defaults for most websites:

### Should Almost Always Be Disabled These features are rarely needed and should be blocked by default:

camera=()                  # Webcam access
microphone=()              # Microphone access
geolocation=()             # Location access
midi=()                    # MIDI device access
usb=()                     # USB device access
bluetooth=()               # Bluetooth access
serial=()                  # Serial port access
hid=()                     # Human Interface Devices
magnetometer=()            # Magnetometer sensor
gyroscope=()               # Gyroscope sensor
accelerometer=()           # Accelerometer sensor

### Context-Dependent Enable these based on your application's needs:

payment=(self)             # Payment Request API — enable for e-commerce
fullscreen=(self)          # Fullscreen API — enable for media players
picture-in-picture=(self)  # PiP — enable for video content
autoplay=(self)            # Media autoplay — enable for video sites
display-capture=()         # Screen capture — usually disable

### Advertising-Related Control features commonly used by ad tech:

attribution-reporting=()   # Conversion tracking
browsing-topics=()         # Topics API (Privacy Sandbox)
interest-cohort=()         # FLoC (deprecated but still worth blocking)

Setting It Up

Nginx

nginx
server {
    listen 443 ssl http2;
    server_name example.com;

    # Permissions-Policy — block unused browser features
    add_header Permissions-Policy "camera=(), microphone=(), geolocation=(), midi=(), usb=(), magnetometer=(), gyroscope=(), accelerometer=(), payment=(), autoplay=(self), fullscreen=(self)" always;

    # ... rest of your config
}

Apache

apache
# In httpd.conf, .htaccess, or VirtualHost block
Header always set Permissions-Policy "camera=(), microphone=(), geolocation=(), midi=(), usb=(), magnetometer=(), gyroscope=(), accelerometer=(), payment=(), autoplay=(self), fullscreen=(self)"

Make sure mod_headers is enabled:

bash
a2enmod headers
systemctl restart apache2

Cloudflare

Using Cloudflare Transform Rules:

  1. 1Go to Rules > Transform Rules > Modify Response Header
  2. 2Create a new rule
  3. 3Set Header name: Permissions-Policy
  4. 4Set Value: camera=(), microphone=(), geolocation=(), midi=(), usb=()
  5. 5Apply to all requests or specific hostnames

Next.js

In next.config.js:

javascript
const nextConfig = {
  async headers() {
    return [
      {
        source: '/(.*)',
        headers: [
          {
            key: 'Permissions-Policy',
            value: 'camera=(), microphone=(), geolocation=(), midi=(), usb=(), magnetometer=(), gyroscope=(), accelerometer=()',
          },
        ],
      },
    ];
  },
};

module.exports = nextConfig;

Vercel

In vercel.json:

json
{
  "headers": [
    {
      "source": "/(.*)",
      "headers": [
        {
          "key": "Permissions-Policy",
          "value": "camera=(), microphone=(), geolocation=(), midi=(), usb=()"
        }
      ]
    }
  ]
}

Testing Your Permissions-Policy

### Browser DevTools 1. Open DevTools (F12) 2. Go to the Network tab 3. Click on the main document request 4. Look at the Response Headers section for Permissions-Policy

### JavaScript API You can query your permissions programmatically:

javascript
// Check if a feature is allowed
navigator.permissions.query({ name: 'camera' }).then(result => {
  console.log('Camera permission:', result.state);
  // "denied" if Permissions-Policy blocks it
});

### Online Tools Run a scan on ZeriFlow to check your Permissions-Policy header alongside all other security headers in one test.

Curl

bash
curl -s -D - -o /dev/null https://example.com | grep -i permissions-policy

Common Configuration Examples

Static Marketing Website

Permissions-Policy: camera=(), microphone=(), geolocation=(), midi=(), usb=(), payment=(), autoplay=(), fullscreen=(self)

E-Commerce Store

Permissions-Policy: camera=(), microphone=(), geolocation=(self), midi=(), usb=(), payment=(self "https://js.stripe.com"), autoplay=(), fullscreen=(self)

Video Platform

Permissions-Policy: camera=(self), microphone=(self), geolocation=(), midi=(), usb=(), payment=(self), autoplay=(self), fullscreen=(self), picture-in-picture=(self)

SaaS Application

Permissions-Policy: camera=(), microphone=(), geolocation=(), midi=(), usb=(), payment=(self), autoplay=(), fullscreen=(self), clipboard-write=(self)

Security Benefits

Configuring Permissions-Policy provides several concrete security benefits:

  1. 1Reduces attack surface — Disabled features can't be exploited, even if an attacker injects code into your page
  2. 2Controls third-party behavior — Embedded iframes and scripts are bound by your policy, preventing surprise feature usage
  3. 3Defense in depth — Works alongside CSP and other headers to create multiple layers of protection
  4. 4Privacy protection — Blocks tracking features used by advertising scripts
  5. 5Compliance signal — Demonstrates proactive security practices for audits

Combined with Content-Security-Policy and other security headers, Permissions-Policy is part of a comprehensive browser security strategy. Check your full header configuration with a ZeriFlow scan to see how all your headers work together.

Ready to check your site?

Run a free security scan in 30 seconds.

Related articles

Keep reading