FFmpeg Auth Headers: How to Use Authorization Bearer

You need to download or process a video that's behind authentication. Maybe it's an HLS stream from a CDN, a DASH manifest on a protected endpoint, or just a direct MP4 URL that requires a bearer token. FFmpeg can handle all of these, but the -headers syntax trips people up.
This post shows you the exact commands that work.
The Basic Syntax
FFmpeg's -headers flag lets you pass custom HTTP headers when fetching remote URLs. For bearer token auth, it looks like this:
ffmpeg -headers "Authorization: Bearer YOUR_TOKEN_HERE" -i https://example.com/protected/video.mp4 -c copy output.mp4
Two things to notice. First, there is no \r\n at the end. Plenty of answers tell you to type one, and in bash or zsh that breaks the request. Inside ordinary quotes the shell passes \r\n through as four plain characters, they land on the end of your token, and the server answers 401. FFmpeg adds the line ending itself. When it does, it prints No trailing CRLF found in HTTP header. Adding it. and that warning is harmless.
Second, the -headers flag must come before the -i input flag. FFmpeg applies an option to the input that follows it, so headers declared after the input URL are never sent.
Downloading a Protected HLS Stream
HLS streams are the most common use case. Your video player can handle the auth token in JavaScript, but you need FFmpeg to do the same thing server-side.
ffmpeg -headers "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..." \
-i https://cdn.example.com/stream/master.m3u8 \
-c copy \
-bsf:a aac_adtstoasc \
output.mp4
FFmpeg applies the -headers to every HTTP request it makes for that input. So when it fetches the playlist and then each .ts segment, the bearer token goes with every request. You don't need to handle segments individually.
The -bsf:a aac_adtstoasc bitstream filter is there because HLS segments often use ADTS-wrapped AAC audio, and MP4 containers need the raw AAC stream. Without it, you might get audio playback issues in some players.
Multiple Headers
Some APIs need more than just authorization. All headers go in a single -headers value, with a real line break between them. In bash and zsh, $'...' quoting turns \r\n into that line break:
ffmpeg -headers $'Authorization: Bearer YOUR_TOKEN\r\nX-Custom-Header: some-value\r\n' \
-i https://api.example.com/video/12345 \
-c copy output.mp4
Don't use multiple -headers flags. FFmpeg only reads the last one, so your earlier headers would be silently ignored.
A variable does not expand inside $'...'. When the token is in a variable, join the pieces:
ffmpeg -headers "Authorization: Bearer $TOKEN"$'\r\n'"X-Custom-Header: some-value" \
-i https://api.example.com/video/12345 \
-c copy output.mp4
Basic Auth
Some older services use HTTP Basic in place of a bearer token. The shortest form puts the credentials in the URL:
ffmpeg -i "https://username:password@example.com/video.mp4" -c copy output.mp4
FFmpeg sends them once the server asks, which is the normal Basic handshake. To send the header up front:
ffmpeg -headers "Authorization: Basic $(printf 'username:password' | base64)" \
-i https://example.com/video.mp4 \
-c copy output.mp4
DASH Streams
The same flag applies to a DASH manifest:
ffmpeg -headers "Authorization: Bearer YOUR_TOKEN" \
-i https://cdn.example.com/dash/manifest.mpd \
-c copy \
-movflags +faststart \
output.mp4
DASH input needs an FFmpeg build that includes the DASH demuxer. Check yours with ffmpeg -demuxers | grep dash. The -movflags +faststart moves the MP4 metadata to the beginning of the file, which is useful if you plan to serve the output over HTTP. Players can start playback before the full download finishes.
Common Gotchas
Quoting in different shells. The form with no line ending is the one that works everywhere, because FFmpeg adds the ending itself:
# bash, zsh, Windows cmd, PowerShell
ffmpeg -headers "Authorization: Bearer MY_TOKEN" -i url -c copy out.mp4
# bash and zsh: a real line ending, for several headers in one value
ffmpeg -headers $'Authorization: Bearer MY_TOKEN\r\n' -i url -c copy out.mp4
# Broken in bash and zsh: \r\n in ordinary quotes is sent as text, single or double
ffmpeg -headers "Authorization: Bearer MY_TOKEN\r\n" -i url -c copy out.mp4
In PowerShell, a real line ending is written with backticks: ` "Authorization: Bearer MY_TOKENrn" `.
Token in a variable. When the token comes from an environment variable or script output:
TOKEN=$(curl -s https://auth.example.com/token | jq -r '.access_token')
ffmpeg -headers "Authorization: Bearer $TOKEN" \
-i https://cdn.example.com/stream.m3u8 \
-c copy output.mp4
The header isn't being sent. Run FFmpeg with -v debug to see the actual HTTP requests. Look for your header in the output:
ffmpeg -v debug -headers "Authorization: Bearer YOUR_TOKEN" \
-i https://example.com/video.mp4 -c copy output.mp4 2>&1 | grep -i auth
If the header doesn't show up in the request, the most likely cause is -headers placed after -i. If it shows up with \r\n written on the end of the value, your shell passed those characters through. Take them out.
Is it FFmpeg at all? Before changing more flags, send the same header with curl:
curl -H "Authorization: Bearer YOUR_TOKEN" "https://example.com/video.mp4" -o /dev/null -w "%{http_code}\n"
If curl also gets a 401, the problem is the token or the URL.
Token expiration during long downloads. Bearer tokens expire. If you're downloading a long HLS stream with hundreds of segments, your token might expire partway through. FFmpeg won't refresh it. You'll see 401 or 403 errors in the output once the token dies.
For short clips, this isn't a problem. But for streams longer than your token's TTL, you need either a long-lived token or a wrapper script that re-authenticates and resumes.
Redirects carry your headers along. Some CDNs redirect your initial request to a different domain. FFmpeg follows the redirect and sends your custom headers to the new location too, even on another host. That keeps a CDN redirect working. It also means your token goes wherever the first server points, so if you don't control the redirect target, request the final URL directly.
Python and Node.js
From code, pass the arguments as a list. Nothing goes through a shell, so there is no quoting to get wrong.
import subprocess
token = "your-bearer-token-here"
url = "https://api.example.com/videos/stream.m3u8"
subprocess.run([
"ffmpeg",
"-headers", f"Authorization: Bearer {token}",
"-i", url,
"-c", "copy",
"output.mp4",
])
const { execFileSync } = require('child_process');
const token = 'your-bearer-token-here';
const url = 'https://api.example.com/videos/stream.m3u8';
execFileSync('ffmpeg', ['-headers', `Authorization: Bearer ${token}`, '-i', url, '-c', 'copy', 'output.mp4']);
When CLI Headers Get Painful
Managing tokens, handling expiration, and dealing with shell quoting across different environments gets old fast. If you're building video processing into an application and not running one-off commands, it can be simpler to keep the auth out of FFmpeg altogether.
FFmpeg Micro runs FFmpeg behind a REST API. It fetches the source from a URL and does not send custom headers, so for a protected file you give it a URL it can read: a signed URL from your storage or CDN, or a file you upload first with a presigned upload. The job itself is one JSON request:
curl -X POST https://api.ffmpeg-micro.com/v1/transcodes \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"inputs": [{ "url": "https://cdn.example.com/video.mp4?signature=..." }],
"outputFormat": "mp4"
}'
No FFmpeg install, and no header quoting on the source side.
Last verified: 2026-10-06 against FFmpeg 8.0.1 in bash and zsh on macOS, with a local server recording the headers it received. The DASH command, the Windows forms and the API request were not run.
About Javid Jamae
Founder & CEO at FFmpeg Micro
Javid is a software engineer, author, and entrepreneur with over 25 years of professional software development experience across enterprise, startup, and consulting environments. He founded FFmpeg Micro to make video processing accessible to developers through a simple, automation-first REST API.
You might also like

How to Use FFmpeg with React Native After FFmpegKit's Retirement
ffmpeg-kit-react-native is retired and its binaries 404. What each FFmpeg React Native option can still do, and how to offload the encode server side.

FFmpeg "Impossible to Convert": Fix the Format, Not the Filter
FFmpeg impossible to convert between the formats supported by the filter? Two branches of your graph disagree on pixel format. Fix it before the overlay.

The OpenAI Agents API can't cut video. MCP gives it FFmpeg.
OpenAI Agents API MCP setup: wire an MCP server into your agent so it can trim, caption and reframe video, plus handling async video jobs without blocking.
Ready to process videos at scale?
Start using FFmpeg Micro's simple API today. No infrastructure required.
Get Started Free