YouTube Membership Video Download Guide
Overview
This guide documents the complete process for downloading YouTube membership-restricted videos using AWS EC2, Docker containers, yt-dlp, and various anti-bot bypass techniques. This method is necessary because YouTube membership videos have strict protections including geo-restrictions, bot detection, SABR streaming protocol enforcement, and JavaScript challenges.
Success Requirements:
- EC2 instance in a region where the video is available (Hong Kong worked for Chinese content)
- Fresh browser cookies from authenticated YouTube session
- PO (Proof of Origin) token generation via bgutil
- JavaScript challenge solver with remote components enabled
- Deno runtime for challenge solving
- ffmpeg for merging separate video/audio streams
Prerequisites
Local Machine
- AWS CLI configured with credentials
- SSH key pair for EC2 access
- Valid YouTube Premium/Membership account
- Browser with cookie export capability (Chrome, Edge, Firefox)
AWS Account Requirements
- EC2 access in target region (ap-east-1 Hong Kong for this case)
- Region must be enabled in AWS account (Hong Kong requires opt-in)
- Appropriate IAM permissions for EC2 operations
Architecture Components
1. EC2 Instance
- Instance Type: t3.medium (2 vCPU, 4GB RAM minimum)
- t3.micro proved too weak for browser operations
- Browser would hang/struggle with insufficient resources
- AMI: Amazon Linux 2023
- Region: ap-east-1 (Hong Kong) - chosen based on content availability
- Security Group: Allow inbound on ports 22 (SSH), 5800 (VNC web), 4416 (bgutil)
2. Docker Containers
Firefox Container (jlesage/firefox:latest)
- Provides VNC-accessible Firefox browser
- Ports: 5800 (web VNC), 5900 (VNC)
- Used for:
- Manual YouTube login
- Cookie generation
- Cookie extraction for yt-dlp
bgutil PO Token Provider (brainicism/bgutil-ytdlp-pot-provider)
- Generates Proof of Origin tokens
- Required to bypass YouTube's 2026 anti-bot measures
- Port: 4416
- Must be accessible from Firefox container (not bound to 127.0.0.1)
3. Key Software Components
yt-dlp (2026.08.19 or later)
- Python-based YouTube downloader
- Requires multiple bypass mechanisms for membership content
Deno (2.7.4 or later)
- JavaScript/TypeScript runtime
- Required for solving YouTube's n-challenge
- Must have remote component downloads enabled
ffmpeg
- Required for merging separate video/audio streams
- YouTube serves 1080p as separate video (mp4) and audio (m4a) streams
Step-by-Step Setup
Phase 1: Launch EC2 Instance
# 1. Find appropriate AMI for Amazon Linux 2023 in Hong Kong region
AMI_ID=$(aws ec2 describe-images \
--region ap-east-1 \
--owners amazon \
--filters "Name=name,Values=al2023-ami-2023.*-x86_64" \
--query 'Images | sort_by(@, &CreationDate) | [-1].ImageId' \
--output text)
# 2. Create security group
SECURITY_GROUP_ID=$(aws ec2 create-security-group \
--region ap-east-1 \
--group-name youtube-downloader-sg \
--description "Security group for YouTube downloader" \
--output text)
# 3. Add inbound rules
aws ec2 authorize-security-group-ingress \
--region ap-east-1 \
--group-id $SECURITY_GROUP_ID \
--protocol tcp --port 22 --cidr 0.0.0.0/0
aws ec2 authorize-security-group-ingress \
--region ap-east-1 \
--group-id $SECURITY_GROUP_ID \
--protocol tcp --port 5800 --cidr 0.0.0.0/0
aws ec2 authorize-security-group-ingress \
--region ap-east-1 \
--group-id $SECURITY_GROUP_ID \
--protocol tcp --port 4416 --cidr 0.0.0.0/0
# 4. Create or import SSH key pair
aws ec2 create-key-pair \
--region ap-east-1 \
--key-name yt-hk-key \
--query 'KeyMaterial' \
--output text > /tmp/yt-hk-key.pem
chmod 400 /tmp/yt-hk-key.pem
# 5. Launch instance
INSTANCE_ID=$(aws ec2 run-instances \
--region ap-east-1 \
--image-id $AMI_ID \
--instance-type t3.medium \
--key-name yt-hk-key \
--security-group-ids $SECURITY_GROUP_ID \
--block-device-mappings 'DeviceName=/dev/xvda,Ebs={VolumeSize=20,VolumeType=gp3}' \
--tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=youtube-downloader}]' \
--query 'Instances[0].InstanceId' \
--output text)
# 6. Wait for instance to be running
aws ec2 wait instance-running --region ap-east-1 --instance-ids $INSTANCE_ID
# 7. Get public IP
PUBLIC_IP=$(aws ec2 describe-instances \
--region ap-east-1 \
--instance-ids $INSTANCE_ID \
--query 'Reservations[0].Instances[0].PublicIpAddress' \
--output text)
echo "Instance ready at: $PUBLIC_IP"
Phase 2: Install Docker and Containers
# SSH into instance
ssh -i /tmp/yt-hk-key.pem -o StrictHostKeyChecking=no ec2-user@$PUBLIC_IP
# Install Docker
sudo yum install -y docker
sudo systemctl start docker
sudo systemctl enable docker
sudo usermod -aG docker ec2-user
# Start Firefox VNC container
sudo docker run -d \
--name firefox \
-p 5800:5800 \
-p 5900:5900 \
-v /home/ec2-user/firefox-data:/config \
--shm-size 2g \
jlesage/firefox:latest
# Start bgutil PO token provider (NOT bound to 127.0.0.1)
sudo docker run -d \
--name bgutil-provider \
-p 4416:4416 \
brainicism/bgutil-ytdlp-pot-provider:latest
# Verify containers are running
sudo docker ps
Phase 3: Configure Firefox Container
# Install required software in Firefox container
sudo docker exec firefox sh -c '
# Add Python repository
apk add --no-cache python3 py3-pip git
# Install yt-dlp
pip3 install --break-system-packages yt-dlp
# Install Deno for JavaScript challenge solving
apk add --no-cache curl unzip
curl -fsSL https://deno.land/install.sh | sh
ln -s /root/.deno/bin/deno /usr/bin/deno
# Verify installations
python3 --version
python3 -m yt_dlp --version
deno --version
'
# Create Firefox profile directory structure
sudo docker exec firefox sh -c '
mkdir -p /root/.mozilla/firefox
ln -s /config/profile /root/.mozilla/firefox/default
cat > /root/.mozilla/firefox/profiles.ini <<EOF
[General]
StartWithLastProfile=1
[Profile0]
Name=default
IsRelative=1
Path=default
Default=1
EOF
'
Phase 4: Browser Login and Cookie Extraction
Option A: Manual Login via VNC
- Access Firefox via web browser:
http://$PUBLIC_IP:5800 - Navigate to YouTube and log in with membership account
- Verify you can access the membership video
- Cookies are now stored in
/config/profile(persisted)
Option B: Import Cookies from Local Browser
Export cookies from your local browser:
-
Install cookie export extension:
- Chrome/Edge: "Cookie-Editor" or "EditThisCookie"
- Firefox: "Cookie Quick Manager"
-
Navigate to YouTube (must be logged in)
-
Export cookies in Netscape format
- Format: Netscape HTTP Cookie File
- Include: All YouTube cookies (
.youtube.comdomain)
-
Copy exported cookies to EC2:
# On local machine - save exported cookies to /tmp/youtube_cookies.txt
scp -i /tmp/yt-hk-key.pem /tmp/youtube_cookies.txt ec2-user@$PUBLIC_IP:/tmp/
# On EC2 instance
ssh -i /tmp/yt-hk-key.pem ec2-user@$PUBLIC_IP
sudo docker cp /tmp/youtube_cookies.txt firefox:/config/cookies.txt
Important cookies that must be present:
SID- Session IDSSID- Secure Session ID__Secure-1PSID- Primary secure session__Secure-3PSID- Third-party secure sessionLOGIN_INFO- Authentication tokenVISITOR_INFO1_LIVE- Visitor trackingSAPISID,__Secure-1PAPISID,__Secure-3PAPISID- API session IDs
Phase 5: Install Official yt-dlp Companion (Optional but Recommended)
The companion extension can automate downloads with embedded subtitles and thumbnails.
sudo docker exec firefox sh -c '
# Install Node.js and npm
apk add --no-cache nodejs npm
# Install companion globally
npm install -g @yt-dlp/web-companion
# Create config directory
mkdir -p /root/.config/vm-yt-dlp
# Start companion bridge (generates token)
nohup vm-yt-dlp > /tmp/companion.log 2>&1 &
sleep 2
cat /root/.config/vm-yt-dlp/config.json
'
Note the pairing token - this is needed to connect browser extension to the companion bridge.
To install browser extension in VNC Firefox:
- Go to
about:addons - Search for "VM yt-dlp"
- Install extension
- Enter pairing token when prompted
Downloading Videos
Critical: The Complete Working Command
sudo docker exec firefox sh -c '
cd /config/downloads
python3 -m yt_dlp \
--cookies-from-browser firefox:/root/.mozilla/firefox/default \
--remote-components ejs:github \
--format "137+140/best[ext=mp4]" \
--merge-output-format mp4 \
--output "video_output.mp4" \
"https://www.youtube.com/watch?v=VIDEO_ID"
'
Key Flags Explained
--cookies-from-browser firefox:/root/.mozilla/firefox/default
- Extracts cookies directly from Firefox profile
- Alternative:
--cookies /config/cookies.txtfor Netscape format file - Must have fresh, valid authentication cookies
--remote-components ejs:github
⚠️ CRITICAL - This is the key that makes it work
- Downloads JavaScript challenge solver components from GitHub
- Required for solving YouTube's n-challenge
- Without this, you get: "The page needs to be reloaded" error
- Deno must be installed and in PATH
--extractor-args "youtube:pot=bgutil:http:base_url=http://172.17.0.1:4416"
- Connects to bgutil PO token provider
172.17.0.1is Docker bridge IP (accessible from containers)- Required to bypass YouTube's 2026 bot detection
- Note: Often not needed if remote components are enabled
--format "137+140/best[ext=mp4]"
137= 1080p video (h264)140= Medium quality audio (m4a, AAC)- Falls back to best single file MP4 if merging fails
- For 720p: use
136+140 - For best available: use
best[ext=mp4](may only get 360p for format 18)
Format Selection Reference
Common format codes for membership videos:
18- 360p MP4 with audio (single file, always works)136- 720p video only (h264)137- 1080p video only (h264)140- 128k audio (m4a, AAC)251- 160k audio (webm, Opus)
List available formats:
python3 -m yt_dlp \
--cookies-from-browser firefox:/root/.mozilla/firefox/default \
--remote-components ejs:github \
--list-formats \
"https://www.youtube.com/watch?v=VIDEO_ID"
Merging Separate Video and Audio Streams
If you downloaded video and audio separately, or merging failed:
sudo docker exec firefox sh -c '
# Install ffmpeg if not already present
apk add ffmpeg
# Merge video + audio
cd /config/downloads
ffmpeg -i video.mp4 -i audio.m4a \
-c copy \
-movflags +faststart \
output_final.mp4
'
Flags explained:
-c copy- Stream copy (no re-encoding, fast)-movflags +faststart- Move metadata to beginning (web streaming optimization)
Retrieving Downloaded Files
Copy from Container to EC2
sudo docker cp firefox:/config/downloads/video_output.mp4 /tmp/
Download to Local Machine
# From local machine
scp -i /tmp/yt-hk-key.pem ec2-user@$PUBLIC_IP:/tmp/video_output.mp4 ./
# Or for WSL to Windows Desktop
scp -i /tmp/yt-hk-key.pem ec2-user@$PUBLIC_IP:/tmp/video_output.mp4 /tmp/
mv /tmp/video_output.mp4 /mnt/c/Users/USERNAME/Desktop/
Retrieving Companion Downloads
If using the official companion extension:
# Check download directory
sudo docker exec firefox ls -lh /root/Downloads/YouTube/
# Files will have full titles with metadata
sudo docker cp "firefox:/root/Downloads/YouTube/Video Title [VIDEO_ID].mp4" /tmp/
# May also include:
# - .srt subtitle files
# - .info.json metadata
# - .jpg thumbnail
Verification and Quality Check
# Inside container with ffmpeg
sudo docker exec firefox sh -c '
ffprobe -v error \
-show_entries stream=codec_type,codec_name,width,height,bit_rate \
-of default=noprint_wrappers=1 \
/config/downloads/video_output.mp4
'
Expected output for 1080p:
codec_name=h264
codec_type=video
width=1920
height=818 (or 1080, depends on aspect ratio)
codec_name=aac
codec_type=audio
Troubleshooting
Error: "The page needs to be reloaded"
Cause: JavaScript challenge solving failed due to missing remote components.
Symptoms:
WARNING: [youtube] [jsc] Remote components challenge solver script (deno) and NPM package (deno) were skipped
WARNING: [youtube] n challenge solving failed
ERROR: [youtube] VIDEO_ID: The page needs to be reloaded
Solution:
- Add
--remote-components ejs:githubflag (critical) - Ensure Deno is installed:
deno --version - Check Deno is in PATH:
which deno - Verify internet access for downloading components
Alternative clients to try if still failing:
# Try different player clients
--extractor-args "youtube:player_client=android"
--extractor-args "youtube:player_client=ios"
--extractor-args "youtube:player_client=tv_embedded"
Error: "Could not find firefox cookies in database"
Cause: Firefox profile not properly linked or symlink broken.
Solution:
sudo docker exec firefox sh -c '
# Create proper directory structure
mkdir -p /root/.mozilla/firefox
# Create symlink to persistent profile
rm -f /root/.mozilla/firefox/default
ln -s /config/profile /root/.mozilla/firefox/default
# Create profiles.ini
cat > /root/.mozilla/firefox/profiles.ini <<EOF
[General]
StartWithLastProfile=1
[Profile0]
Name=default
IsRelative=1
Path=default
Default=1
EOF
# Verify cookies database exists
ls -la /config/profile/cookies.sqlite
'
Error: "Only images available" or "Some formats may be missing"
Cause: SABR streaming forced, or PO token provider not accessible.
Symptoms:
WARNING: [youtube] Some web client https formats have been skipped as they are missing a URL
YouTube is forcing SABR streaming for this client
Solutions:
- Check bgutil provider is running:
sudo docker ps | grep bgutil
curl http://172.17.0.1:4416/health # From inside Firefox container
- Restart bgutil with correct port binding:
sudo docker stop bgutil-provider
sudo docker rm bgutil-provider
sudo docker run -d \
--name bgutil-provider \
-p 4416:4416 \
brainicism/bgutil-ytdlp-pot-provider:latest
- Add PO token extractor args:
--extractor-args "youtube:pot=bgutil:http:base_url=http://172.17.0.1:4416"
- Try with remote components (may bypass need for PO tokens):
--remote-components ejs:github
Error: Cookies Expired or Invalid
Symptoms:
ERROR: [youtube] VIDEO_ID: Could not read this video. Join this channel to get access to members-only content
Cause: YouTube rotates session tokens frequently (can be hours or days).
Solution:
- Re-export cookies from authenticated browser session
- Ensure you're exporting from a session that can actually view the video
- Verify cookie file includes all required authentication cookies (SID, SSID, LOGIN_INFO, etc.)
- Check cookie file format is correct (Netscape format)
Verify cookies in Firefox profile:
sudo docker exec firefox sh -c '
python3 -m yt_dlp --cookies-from-browser firefox:/root/.mozilla/firefox/default --print-traffic
' 2>&1 | grep -i cookie
Error: "WARNING: You have requested merging of multiple formats but ffmpeg is not installed"
Symptoms:
- Downloads complete but results in separate
.f137.mp4(video) and.f140.m4a(audio) files - No merged output file
Solution:
# Install ffmpeg in container
sudo docker exec firefox apk add ffmpeg
# Manually merge files
sudo docker exec firefox sh -c '
cd /config/downloads
ffmpeg -i video.f137.mp4 -i audio.f140.m4a -c copy -movflags +faststart merged.mp4
'
Error: Instance Too Weak / Browser Hanging
Symptoms:
- Firefox VNC interface unresponsive
- Browser tabs freezing
- Very slow page loads
Cause: t3.micro or t3.small instances have insufficient resources.
Solution:
- Stop instance:
aws ec2 stop-instances --region ap-east-1 --instance-ids $INSTANCE_ID - Change instance type:
aws ec2 modify-instance-attribute --region ap-east-1 --instance-id $INSTANCE_ID --instance-type t3.medium - Start instance:
aws ec2 start-instances --region ap-east-1 --instance-ids $INSTANCE_ID
Recommended: t3.medium (2 vCPU, 4GB RAM)
Error: Video Not Available in Region
Symptoms:
- "Video unavailable"
- "This video is not available in your country"
Solution:
Test different AWS regions:
# Common regions for geo-restricted content:
# ap-east-1 (Hong Kong) - Chinese content
# ap-northeast-1 (Tokyo) - Japanese content
# ap-northeast-2 (Seoul) - Korean content
# ap-southeast-1 (Singapore) - Southeast Asian content
# eu-west-2 (London) - European content
Quick region test script:
for region in ap-east-1 ap-northeast-1 ap-northeast-2 ap-southeast-1; do
echo "Testing $region..."
# Launch test instance, attempt download, terminate
done
Error: Cannot Connect to bgutil Provider
Symptoms:
[pot:bgutil:http] Failed to generate PO Token
Connection refused to http://172.17.0.1:4416
Debug steps:
# 1. Check container is running
sudo docker ps | grep bgutil
# 2. Check port binding (should show 0.0.0.0:4416)
sudo docker port bgutil-provider
# 3. Test from Firefox container
sudo docker exec firefox wget -O- http://172.17.0.1:4416/health
# 4. Check Docker network
sudo docker network inspect bridge | grep -A 3 "Gateway"
Common mistake: Binding to 127.0.0.1:4416:4416 instead of 4416:4416
- Correct:
-p 4416:4416(accessible from other containers via 172.17.0.1) - Wrong:
-p 127.0.0.1:4416:4416(only accessible from host)
Error: Deno Not Found or Challenge Solver Failed
Symptoms:
[youtube] [jsc:deno] Running deno: /usr/bin/deno run ...
sh: /usr/bin/deno: not found
Solution:
sudo docker exec firefox sh -c '
# Install Deno
curl -fsSL https://deno.land/install.sh | sh
# Create symlink to PATH
ln -s /root/.deno/bin/deno /usr/bin/deno
# Verify
deno --version
which deno
'
Companion Extension Connection Issues
Symptoms:
- "Pairing token too short" error
- Cannot connect to bridge
Solutions:
- Check companion is running:
sudo docker exec firefox ps aux | grep vm-yt-dlp
- Restart companion bridge:
sudo docker exec firefox sh -c '
pkill -f vm-yt-dlp
nohup vm-yt-dlp > /tmp/companion.log 2>&1 &
sleep 2
cat /root/.config/vm-yt-dlp/config.json
'
- Check config file for token:
sudo docker exec firefox cat /root/.config/vm-yt-dlp/config.json
Token should be ~50 characters long. If shorter or missing, companion didn't start properly.
Performance: Slow Download Speeds
Causes and solutions:
-
Throttled by YouTube:
- Use
--limit-rate 5Mto stay under radar - Add delays:
--sleep-interval 1 --max-sleep-interval 3
- Use
-
Network congestion:
- Try different AWS regions
- Use enhanced networking instance types
-
Fragment download issues:
- Increase concurrent fragments:
--concurrent-fragments 4 - Or decrease if causing errors:
--concurrent-fragments 1
- Increase concurrent fragments:
Debug Mode
For any persistent issues, run with verbose debugging:
python3 -m yt_dlp \
--cookies-from-browser firefox:/root/.mozilla/firefox/default \
--remote-components ejs:github \
--verbose \
--print-traffic \
"https://www.youtube.com/watch?v=VIDEO_ID" \
2>&1 | tee debug.log
Look for:
- Cookie extraction count (should be 70+ cookies)
- PO token generation attempts
- JavaScript challenge solver execution
- Available format list
- Any WARNING or ERROR messages
Cost Considerations
AWS Costs
EC2 Instance (t3.medium in ap-east-1 Hong Kong):
- On-Demand: ~$0.0608/hour
- For a 1-hour download session: ~$0.06
- For 8-hour workday: ~$0.49
Data Transfer:
- EC2 to Internet: $0.12/GB (first 10 GB free per month)
- For 1 GB video: ~$0.12
EBS Storage:
- 20 GB gp3: ~$0.0096/day
- Usually negligible for short-term use
Total estimated cost for single video download: $0.20 - $0.50
Cost Optimization
- Stop instance when not in use:
aws ec2 stop-instances --region ap-east-1 --instance-ids $INSTANCE_ID
- Use Spot Instances (70% cheaper):
aws ec2 run-instances \
--instance-market-options 'MarketType=spot' \
... other parameters ...
- Terminate after completion:
aws ec2 terminate-instances --region ap-east-1 --instance-ids $INSTANCE_ID
Cleanup
After Download Complete
# 1. Download files to local machine first!
# 2. Stop containers
ssh -i /tmp/yt-hk-key.pem ec2-user@$PUBLIC_IP '
sudo docker stop firefox bgutil-provider
sudo docker rm firefox bgutil-provider
'
# 3. Terminate EC2 instance
aws ec2 terminate-instances --region ap-east-1 --instance-ids $INSTANCE_ID
# 4. Delete security group (wait for instance to terminate first)
aws ec2 wait instance-terminated --region ap-east-1 --instance-ids $INSTANCE_ID
aws ec2 delete-security-group --region ap-east-1 --group-id $SECURITY_GROUP_ID
# 5. Delete key pair
aws ec2 delete-key-pair --region ap-east-1 --key-name yt-hk-key
rm /tmp/yt-hk-key.pem
Quick Reference: Complete Working Example
# After setup is complete, this is the command that works:
ssh -i /tmp/yt-hk-key.pem ec2-user@$PUBLIC_IP '
sudo docker exec firefox sh -c "
cd /config/downloads
python3 -m yt_dlp \
--cookies-from-browser firefox:/root/.mozilla/firefox/default \
--remote-components ejs:github \
--format \"137+140/best[ext=mp4]\" \
--merge-output-format mp4 \
--output \"%(title)s.%(ext)s\" \
\"https://www.youtube.com/watch?v=VIDEO_ID\"
"
'
# Download to local
scp -i /tmp/yt-hk-key.pem ec2-user@$PUBLIC_IP:/tmp/*.mp4 ./
Key Takeaways
What Makes This Work
-
Remote Components Flag - The single most critical element. Without
--remote-components ejs:github, JavaScript challenges fail. -
Fresh Cookies - Must be from an authenticated session that can actually view the video. Cookies expire frequently.
-
Proper Region - EC2 instance must be in a region where the content is available.
-
Sufficient Resources - t3.medium minimum for reliable browser operation.
-
Deno Runtime - Required for challenge solving, must be in PATH.
What Doesn't Work
-
Screen recording - Poor quality, timing-dependent, not reliable.
-
Browser extensions alone - Most are outdated or blocked by YouTube.
-
Direct yt-dlp without auth - Immediately blocked for membership content.
-
Weak instances - t3.micro/small cause browser hangs and failures.
-
Client switching without remote components - Trying different player clients doesn't help if JS challenges aren't solved.
Critical Flags Summary
Minimum required:
--cookies-from-browser firefox:/path/to/profile
--remote-components ejs:github
Recommended complete set:
--cookies-from-browser firefox:/root/.mozilla/firefox/default \
--remote-components ejs:github \
--format "137+140/best[ext=mp4]" \
--merge-output-format mp4 \
--concurrent-fragments 4 \
--output "%(title)s.%(ext)s"
Additional Resources
- yt-dlp documentation: https://github.com/yt-dlp/yt-dlp
- bgutil PO token provider: https://github.com/Brainicism/bgutil-ytdlp-pot-provider
- Deno installation: https://deno.land/
- yt-dlp companion: https://github.com/yt-dlp/web-companion
Version Information
This guide was tested with:
- yt-dlp: 2026.08.19
- Deno: 2.7.4
- Python: 3.14.7
- bgutil-ytdlp-pot-provider: 2.0.0
- Firefox container: jlesage/firefox:latest
- Amazon Linux: 2023
- Date: September 2026
Note: YouTube's anti-bot measures evolve constantly. If this method stops working, check for:
- Updated yt-dlp version
- New bypass techniques in yt-dlp issues/discussions
- Updated bgutil provider
- New required components or flags