Download YouTube Videos for Free with yt-dlp

Devin Schumacher is an entrepreneur, internet personality, author, music producer, philanthropist & founder of SERP.
Search for a command to run...

Devin Schumacher is an entrepreneur, internet personality, author, music producer, philanthropist & founder of SERP.
No comments yet. Be the first to comment.
Lost access to your Loom recordings when your subscription ended? You're not alone. Many creators discover they've accumulated dozens of valuable videos only to realize there's no simple "download" bu
How to Download Onlyfans Profile's Videos & Images for FREE (yt-dlp tutorial) 💡 Want the easy way? Get the OnlyFans Downloader extension and save OnlyFans media without commands or DevTools. Follow

How to Download Circle.so Videos for Free (HLS m3u8 Streams) - using yt-dlp Circle.so hosts course videos using HLS streaming, where video data is split into .ts segments and controlled by a .m3u8 pla

How to Download Coursera Videos for FREE (yt-dlp tutorial) 💡 Want the easy way? Get the Coursera Downloader extension and download Coursera videos without commands or DevTools. Follow along with th

How to Download Skool Videos with yt-dlp 💡 Want the easy way? Get the Skool Video Downloader extension and download Skool videos without commands or DevTools. Skool delivers videos over HLS (.m3u8

💡 Want the easy way? Get the YouTube Video Downloader extension and download YouTube videos without commands or DevTools.
For most public YouTube videos, this works fine:
yt-dlp -f "bv*+ba/best" \
--merge-output-format mp4 \
"https://www.youtube.com/watch?v=VIDEO_ID"
-f "bv*+ba/best" → pick best video + best audio.--merge-output-format mp4 → final file is MP4 even if YouTube offers WebM.YouTube serves every video in multiple delivery protocols:
DASH (default) → uses videoplayback?...range=... URLs.
HLS (m3u8 playlists) → chunked .ts/.mp4 streams.
👉 That’s why you sometimes see:
ERROR: unable to download video data: HTTP Error 403: Forbidden
The fix is to have fallback commands to switch protocols and adjust retry behavior.
yt-dlp -f "bv*+ba/best" -N 16 \
--merge-output-format mp4 \
"https://www.youtube.com/watch?v=VIDEO_ID"
-N 16 → download 16 fragments at once (faster).yt-dlp -f "bv*[protocol=m3u8]+ba[protocol=m3u8]/best" \
--hls-prefer-native -N 16 \
--merge-output-format mp4 \
"https://www.youtube.com/watch?v=VIDEO_ID"
.m3u8 streams.yt-dlp -f "bv*[protocol=m3u8]+ba[protocol=m3u8]/best" \
--hls-prefer-native -N 16 \
--retries 20 --fragment-retries 100 \
--merge-output-format mp4 \
"https://www.youtube.com/watch?v=VIDEO_ID"
First install aria2:
brew install aria2
Then run:
yt-dlp -f "bv*+ba/best" \
--downloader aria2c \
--downloader-args "aria2c:-x 16 -s 16 -k 1M --max-tries=0 --retry-wait=5" \
--merge-output-format mp4 \
"https://www.youtube.com/watch?v=VIDEO_ID"
-x 16 -s 16 → 16 parallel connections.Clear cache if signatures go stale:
yt-dlp --rm-cache-dir
Age-restricted or region-locked videos: Use cookies from your browser:
yt-dlp --cookies-from-browser "chrome:Default" "URL"
Specific resolution:
yt-dlp -f "bv*[height=1080]+ba/best" "URL"
yt-dlp is the tool to use for YouTube — there isn’t a better maintained alternative.
Sometimes downloads 403 mid-way because YouTube’s CDN rejects DASH requests.
Solution: keep a 4-step playbook:
With this sequence, you’ll succeed in almost every case.
Great question 👍
Unfortunately, yt-dlp itself doesn’t have a built-in “try this, and if it fails, try that” fallback sequence. It will either succeed or exit with an error. But you can string your four approaches together in one shell command using || (OR chaining).
That way, if the first command fails, the shell runs the next one automatically.
yt-dlp -f "bv*+ba/best" -N 16 --merge-output-format mp4 "https://www.youtube.com/watch?v=VIDEO_ID" \
|| yt-dlp -f "bv*[protocol=m3u8]+ba[protocol=m3u8]/best" --hls-prefer-native -N 16 --merge-output-format mp4 "https://www.youtube.com/watch?v=VIDEO_ID" \
|| yt-dlp -f "bv*[protocol=m3u8]+ba[protocol=m3u8]/best" --hls-prefer-native -N 16 --retries 20 --fragment-retries 100 --merge-output-format mp4 "https://www.youtube.com/watch?v=VIDEO_ID" \
|| yt-dlp -f "bv*+ba/best" --downloader aria2c --downloader-args "aria2c:-x 16 -s 16 -k 1M --max-tries=0 --retry-wait=5" --merge-output-format mp4 "https://www.youtube.com/watch?v=VIDEO_ID"
cmd1 || cmd2 || cmd3 ...
Shell runs cmd1.
So this tries:
Whichever works first will complete the download.
Yep — you can chain them, but it’s cleaner to try max-quality (MKV, AV1/VP9 OK) first, then fall back to MP4-only variants, with HLS + retries, and finally aria2c. Here’s a single paste-and-go you can reuse.
VIDEO_URL="https://www.youtube.com/watch?v=S-mow-gu2XA"; \
yt-dlp -N 16 -f "bestvideo*+bestaudio/best" \
--remux-video mkv "$VIDEO_URL" \
|| yt-dlp -N 16 -f "bv*+ba/best" \
--http-chunk-size 10M --retries 20 --fragment-retries 100 --force-ipv4 \
--remux-video mkv "$VIDEO_URL" \
|| yt-dlp -N 16 -f "bv*[protocol=m3u8]+ba[protocol=m3u8]/best" \
--hls-prefer-native --retries 20 --fragment-retries 100 \
--remux-video mkv "$VIDEO_URL" \
|| yt-dlp -N 16 -f "bv*[ext=mp4][vcodec*=avc1]+ba[ext=m4a]/best[ext=mp4]" \
--merge-output-format mp4 "$VIDEO_URL" \
|| yt-dlp -f "bv*+ba/best" \
--downloader aria2c \
--downloader-args "aria2c:-x 16 -s 16 -k 1M --max-tries=0 --retry-wait=5" \
--merge-output-format mp4 "$VIDEO_URL"
VIDEO_URL once; the chain reuses it.-f "bestvideo*[height=2160]+bestaudio/best".-o "%(title)s.%(ext)s".