n8n is a powerful workflow automation tool that lets you connect APIs, services, and databases. When triggering n8n workflows from browser-based frontends—whether it’s a React app, a simple HTML form, or any JavaScript application—you’ll likely encounter CORS (Cross-Origin Resource Sharing) errors. This guide explains why these errors occur and provides multiple solutions to fix them.
Table of Contents
- Understanding CORS in n8n
- Common CORS Error Messages
- Environment Variable Configuration
- Reverse Proxy Solutions
- Webhook Node Configuration
- Using a CORS Proxy
- Platform-Specific Issues
- Known Bugs and Version Issues
- Best Practices
Understanding CORS in n8n
When your frontend application (running on https://myapp.com) tries to call an n8n webhook (running on https://n8n.myserver.com), the browser performs a security check called CORS. The browser sends a “preflight” OPTIONS request to ask the n8n server if it allows requests from your frontend’s origin.
If n8n doesn’t respond with the correct Access-Control-Allow-Origin header, the browser blocks the request—even though the same request works perfectly in Postman or cURL.
Why Postman Works but Browsers Don’t
This is the most confusing part for many developers. The difference is:
- Postman/cURL: These are not browsers. They don’t enforce CORS because they’re not protecting a user browsing the web.
- Browsers: Enforce CORS to prevent malicious websites from making unauthorized requests on behalf of users.
If your n8n webhook works in Postman but fails from your website, you have a CORS configuration issue—not a connectivity issue.
Common CORS Error Messages
Here are the typical errors you’ll see in the browser console when calling n8n webhooks:
Missing Access-Control-Allow-Origin
Access to fetch at 'https://n8n.example.com/webhook/abc123' from origin
'https://myapp.com' has been blocked by CORS policy: No
'Access-Control-Allow-Origin' header is present on the requested resource.
Preflight Request Failed
Access to XMLHttpRequest at 'https://n8n.example.com/webhook/abc123'
from origin 'https://myapp.com' has been blocked by CORS policy:
Response to preflight request doesn't pass access control check.
Origin Not Allowed
Access to fetch at 'https://n8n.example.com/webhook/abc123' from origin
'https://myapp.com' has been blocked by CORS policy: The
'Access-Control-Allow-Origin' header has a value that is not equal
to the supplied origin.
403 on OPTIONS Request
Failed to load resource: the server responded with a status of 403
This indicates the preflight OPTIONS request was rejected.
Environment Variable Configuration
n8n provides several environment variables to configure CORS. These must be set when starting your n8n instance.
Key Environment Variables
# Allow all origins (use only for testing)
N8N_CORS_ALLOW_ORIGIN=*
# Or specify exact origins (recommended for production)
N8N_CORS_ALLOW_ORIGIN=https://myapp.com,https://www.myapp.com
# Webhook-specific CORS settings
WEBHOOK_CORS_ALLOWED_ORIGINS=https://myapp.com
WEBHOOK_CORS_ALLOWED_METHODS=GET,HEAD,POST,PUT,DELETE,OPTIONS
WEBHOOK_CORS_ALLOWED_HEADERS=Content-Type,Authorization,X-Requested-With,Accept
# Additional settings
N8N_ALLOW_CREDENTIALS=true
N8N_EXPRESS_TRUST_PROXY=true
Docker Compose Example
version: '3.8'
services:
n8n:
image: n8nio/n8n
ports:
- "5678:5678"
environment:
- N8N_CORS_ALLOW_ORIGIN=https://myapp.com
- WEBHOOK_CORS_ALLOWED_ORIGINS=https://myapp.com
- WEBHOOK_CORS_ALLOWED_METHODS=GET,POST,OPTIONS
- WEBHOOK_CORS_ALLOWED_HEADERS=Content-Type,Authorization
- N8N_ALLOW_CREDENTIALS=true
volumes:
- n8n_data:/home/node/.n8n
volumes:
n8n_data:
Important Caveats
Environment variables alone may not solve all CORS issues, particularly:
- Wait Node webhooks: There’s a known bug where Wait node webhook URLs don’t respect CORS settings (see GitHub Issue #18143)
- Preflight handling: Some n8n versions don’t properly handle OPTIONS requests
- Hosting platform restrictions: Some platforms (like Railway) may intercept OPTIONS requests before they reach n8n
Reverse Proxy Solutions
The most reliable way to fix n8n CORS issues is to configure CORS headers at your reverse proxy level. This intercepts requests before they reach n8n and adds the appropriate headers.
Nginx Configuration
server {
listen 443 ssl;
server_name n8n.example.com;
# SSL configuration
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/key.pem;
location / {
# Handle preflight requests
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' 'https://myapp.com' always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS' always;
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization' always;
add_header 'Access-Control-Allow-Credentials' 'true' always;
add_header 'Access-Control-Max-Age' 86400;
add_header 'Content-Type' 'text/plain; charset=utf-8';
add_header 'Content-Length' 0;
return 204;
}
# Add CORS headers to all responses
add_header 'Access-Control-Allow-Origin' 'https://myapp.com' always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS' always;
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization' always;
add_header 'Access-Control-Allow-Credentials' 'true' always;
# Proxy to n8n
proxy_pass http://localhost:5678;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_bypass $http_upgrade;
}
}
Traefik Configuration
For Traefik (commonly used with Docker), add these labels:
services:
n8n:
image: n8nio/n8n
labels:
- "traefik.enable=true"
- "traefik.http.routers.n8n.rule=Host(`n8n.example.com`)"
- "traefik.http.routers.n8n.entrypoints=websecure"
- "traefik.http.routers.n8n.tls.certresolver=letsencrypt"
# CORS middleware
- "traefik.http.middlewares.n8n-cors.headers.accesscontrolallowmethods=GET,POST,PUT,DELETE,OPTIONS"
- "traefik.http.middlewares.n8n-cors.headers.accesscontrolallowheaders=Content-Type,Authorization"
- "traefik.http.middlewares.n8n-cors.headers.accesscontrolalloworiginlist=https://myapp.com"
- "traefik.http.middlewares.n8n-cors.headers.accesscontrolallowcredentials=true"
- "traefik.http.middlewares.n8n-cors.headers.accesscontrolmaxage=86400"
- "traefik.http.routers.n8n.middlewares=n8n-cors"
Caddy Configuration
n8n.example.com {
@cors_preflight method OPTIONS
handle @cors_preflight {
header Access-Control-Allow-Origin "https://myapp.com"
header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS"
header Access-Control-Allow-Headers "Content-Type, Authorization"
header Access-Control-Allow-Credentials "true"
respond "" 204
}
header Access-Control-Allow-Origin "https://myapp.com"
header Access-Control-Allow-Credentials "true"
reverse_proxy localhost:5678
}
Webhook Node Configuration
You can also configure CORS headers directly in your n8n webhook node’s response.
Setting Response Headers
- In your workflow, add a Respond to Webhook node after your webhook trigger
- In the node settings, add custom response headers:
Access-Control-Allow-Origin: https://myapp.com
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Allow-Credentials: true
Limitation: Preflight Requests
Setting headers in the webhook node does not apply to OPTIONS preflight requests. The browser sends the OPTIONS request before your workflow even runs, so n8n must handle it at a lower level.
This is why the reverse proxy approach is more reliable—it catches the OPTIONS request before n8n processes it.
Workflow Must Be Active
A common gotcha: CORS headers only work when your workflow is active, not in test mode. Many users report that switching from test mode to active mode resolves their CORS issues.
To activate:
- Open your workflow
- Toggle the Active switch in the top right
- Use the Production webhook URL, not the test URL
Using a CORS Proxy
When you can’t configure n8n’s server or reverse proxy (e.g., using n8n Cloud or a restricted hosting environment), you can route requests through a CORS proxy like corsproxy.io.
How It Works
Instead of calling the n8n webhook directly, your frontend calls the CORS proxy, which forwards the request to n8n and adds the necessary CORS headers to the response.
JavaScript Implementation
const CORS_PROXY = 'https://corsproxy.io/?url=';
const N8N_WEBHOOK = 'https://your-n8n-instance.com/webhook/abc123';
async function triggerWorkflow(data) {
const response = await fetch(CORS_PROXY + encodeURIComponent(N8N_WEBHOOK), {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
});
return response.json();
}
// Usage
triggerWorkflow({ name: 'John', email: 'john@example.com' })
.then(result => console.log('Workflow triggered:', result))
.catch(error => console.error('Error:', error));
React Example
import { useState } from 'react';
const CORS_PROXY = 'https://corsproxy.io/?url=';
const N8N_WEBHOOK = 'https://your-n8n-instance.com/webhook/contact-form';
function ContactForm() {
const [status, setStatus] = useState('idle');
const handleSubmit = async (e) => {
e.preventDefault();
setStatus('submitting');
const formData = new FormData(e.target);
const data = Object.fromEntries(formData);
try {
const response = await fetch(
CORS_PROXY + encodeURIComponent(N8N_WEBHOOK),
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
}
);
if (response.ok) {
setStatus('success');
} else {
setStatus('error');
}
} catch (error) {
console.error('Submission error:', error);
setStatus('error');
}
};
return (
<form onSubmit={handleSubmit}>
<input name="name" placeholder="Name" required />
<input name="email" type="email" placeholder="Email" required />
<textarea name="message" placeholder="Message" required />
<button type="submit" disabled={status === 'submitting'}>
{status === 'submitting' ? 'Sending...' : 'Send'}
</button>
{status === 'success' && <p>Message sent successfully!</p>}
{status === 'error' && <p>Failed to send. Please try again.</p>}
</form>
);
}
When to Use a CORS Proxy
- n8n Cloud: You can’t configure server-level CORS settings
- Restricted hosting: Platforms that don’t allow reverse proxy configuration
- Quick prototyping: Faster than setting up proper CORS headers
- Third-party n8n instances: When you don’t control the n8n server
Platform-Specific Issues
n8n Cloud
n8n Cloud has limited CORS configuration options. Users report persistent CORS errors even after setting environment variables through the n8n Cloud dashboard.
Workaround: Use a CORS proxy like corsproxy.io to route requests.
Railway
Railway’s infrastructure may intercept OPTIONS preflight requests before they reach your n8n container.
Workaround: Some users report needing to migrate to other platforms like Render for proper CORS support. Alternatively, use a CORS proxy.
Cloudron
Cloudron users have reported CORS issues specific to their environment.
Solution: Configure CORS at the Cloudron reverse proxy level rather than in n8n’s environment variables.
Docker with ngrok
When using ngrok for local development:
# Start n8n with CORS settings
docker run -it --rm \
-p 5678:5678 \
-e N8N_CORS_ALLOW_ORIGIN=* \
-e WEBHOOK_CORS_ALLOWED_ORIGINS=* \
n8nio/n8n
Note: ngrok itself doesn’t add CORS headers, so n8n must be configured to handle them.
Known Bugs and Version Issues
Wait Node CORS Bug
As of early 2025, there’s a confirmed bug (GitHub #18143) where Wait node webhook URLs don’t properly handle CORS preflight requests, even with correct environment variables.
Affected versions: 1.103.0 through 2.1.5+
Symptoms:
- Regular webhook nodes work fine
- Wait node webhook URLs fail with CORS errors
- Direct cURL requests work, but browser requests fail
Workaround: Use a CORS proxy or handle the Wait node callback through your own backend server instead of directly from the browser.
Version Regression
CORS handling regressed in version 1.103.0. If you’re experiencing issues:
- Check if downgrading to 1.102.2 resolves the problem
- Monitor the GitHub issue for fixes
- Use reverse proxy CORS configuration as a reliable workaround
Best Practices
1. Don’t Use Wildcard in Production
# Don't do this in production
N8N_CORS_ALLOW_ORIGIN=*
# Do this instead
N8N_CORS_ALLOW_ORIGIN=https://myapp.com,https://www.myapp.com
Wildcards allow any website to call your webhooks, which is a security risk.
2. Layer Your CORS Configuration
For maximum reliability, configure CORS at multiple levels:
- Reverse proxy (Nginx/Traefik/Caddy) - catches preflight requests
- n8n environment variables - application-level configuration
- Webhook node headers - workflow-specific overrides
3. Test with Browser DevTools
Always test from an actual browser, not just Postman:
- Open DevTools → Network tab
- Trigger your webhook from your frontend
- Look for the OPTIONS preflight request
- Check if it returns 200/204 with correct CORS headers
4. Use Active Mode for Testing
Don’t test CORS with n8n’s test/inactive webhooks. Always:
- Activate your workflow
- Use the production webhook URL
- Test from your actual frontend domain
5. Consider a Backend Proxy
For production applications, consider proxying n8n requests through your own backend:
// Your backend (Node.js/Express)
app.post('/api/trigger-workflow', async (req, res) => {
// No CORS issues - server-to-server request
const response = await fetch('https://n8n.example.com/webhook/abc123', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(req.body)
});
const data = await response.json();
res.json(data);
});
This approach:
- Eliminates CORS issues entirely
- Hides your n8n webhook URL from the client
- Allows you to add authentication and rate limiting
Conclusion
CORS errors in n8n are frustrating but solvable. The key approaches are:
- Configure your reverse proxy (most reliable for self-hosted)
- Set environment variables (works for most scenarios)
- Use a CORS proxy like corsproxy.io (best for n8n Cloud or restricted environments)
- Proxy through your backend (best for production applications)
Given the known bugs with certain n8n versions, using a reverse proxy or CORS proxy service is often the most reliable solution until these issues are fully resolved upstream.