If you’ve exposed Jellyfin to the internet, whether through port forwarding or a reverse proxy, you’re already being scanned. Bots see an open login page and start stuffing passwords, trying to guess their way in.
Ask me how I know.
When I first stood up Jellyfin and exposed it to the internet, I left the default jellyfin username in place. Bots found the login page almost immediately and hammered the account around the clock. I only noticed because Jellyfin’s per-user lockout kept tripping and I couldn’t log in to watch anything. I knew better. And there I was, staring at thousands of failed login attempts from IPs scattered across multiple countries.
So I changed the default username. That stopped the account lockouts. But the server itself was still getting probed every single day. I needed another layer. Fail2Ban was the obvious answer.
This guide covers installing and configuring Fail2Ban for a Jellyfin LXC without banning yourself. We’ll cover direct exposure, reverse proxy setups, Docker-in-LXC gotchas, and monitoring bans over time.
Fail2Ban watches Jellyfin logs for failed login attempts and automatically blocks abusive IPs. With the right jail, filter, and network configuration, you can stop brute-force attacks in their tracks even behind a reverse proxy all without locking yourself out.
Quick Start for Experienced Users:
Create filter /etc/fail2ban/filter.d/jellyfin.conf
Create jail /etc/fail2ban/jail.d/jellyfin.local
Verify firewall backend (iptables or nftables)
Configure reverse proxy trust if needed
Test with fail2ban-regex
Why Fail2Ban Protects Your Jellyfin Server
Here’s the thing: Jellyfin has authentication, and its rate-limit lockout is per user, not global. If you haven’t set it on a user account, an attacker can throw thousands of passwords at it per hour without consequence.

Fail2Ban operates globally. You don’t have to set it per user.
Fail2Ban does this by:
- Watching log files for suspicious patterns
- Counting repeated failures from the same IP
- Adding firewall rules to block that IP for a set time
For context, the Jellyfin default HTTP port is 8096 and the default HTTPS port is 8920. Whether you expose these directly or run Jellyfin behind a reverse proxy on port 443, failed login attempts look the same in the logs. That’s actually good news. One Fail2Ban config works for both scenarios.
Pre-Deployment Checklist
Answer these before touching any configs. Trust me, five minutes now saves an hour of troubleshooting later:
- Exposure method: Direct port forwarding or reverse proxy?
- Jellyfin port: Using default 8096 or custom?
- Firewall backend: iptables or nftables? (Check with
sudo iptables --versionandsudo nft --version) - Container setup: Jellyfin directly in LXC or Docker-in-LXC?
- Reverse proxy: If yes, are real client IPs logged?
- Trusted networks: What IPs should never be banned?
I’ll be configuring this on an unprivileged LXC running Debian 13 with nftables.
All examples below assume that setup.
Checking Your Firewall Backend
Modern Debian systems often run nftables with iptables as a compatibility layer.
You need to know which one Fail2Ban will actually use:
# Check what's actually running
sudo systemctl status nftables
sudo systemctl status netfilter-persistent
Fail2Ban works with both, but the actions you configure will differ.
Get this wrong and bans won’t actually happen.
Installing Fail2Ban on Debian/Ubuntu LXC
Alright, let’s get Fail2Ban installed:
sudo apt update
sudo apt install fail2ban
sudo systemctl status fail2ban
If you see active (running), you’re golden. Fail2Ban starts automatically and survives reboots.
Example:
● fail2ban.service - Fail2Ban Service
Loaded: loaded (/usr/lib/systemd/system/fail2ban.service; enabled; preset: enabled)
Active: active (running) since Wed 2025-12-31 11:22:01 MST; 20h ago
Invocation: 9a7ce4195aaa4dc48dc750363bb9b954
Docs: man:fail2ban(1)
Main PID: 329267 (fail2ban-server)
Tasks: 13 (limit: 7051)
Memory: 15.7M (peak: 24M)
CPU: 4min 37.380s
CGroup: /system.slice/fail2ban.service
└─329267 /usr/bin/python3 /usr/bin/fail2ban-server -xf start
Dec 31 11:22:01 racknerd-ea37d8f systemd[1]: Started fail2ban.service - Fail2Ban Service.
Dec 31 11:22:01 racknerd-ea37d8f fail2ban-server[329267]: Server ready

The hardware doesn’t really matter for Fail2Ban itself. It runs happily on anything that can host the LXC. What matters next is the filter that tells it what a failed login actually looks like.
Creating the Jellyfin Fail2Ban Filter
Filters live in /etc/fail2ban/filter.d/ and tell Fail2Ban what a “failed login” looks like in your logs.
sudo nano /etc/fail2ban/filter.d/jellyfin.conf
Add this:
[Definition]
failregex = ^.*Authentication request for .* has been denied \(IP: "<ADDR>"\)\.
ignoreregex =
Test the Filter
Before going any further:
- Make sure to have some failed logins today
- Test the filter against your actual logs
- REPLACE: YYYYMMDD with today’s date
sudo fail2ban-regex /var/log/jellyfin/jellyfinYYYYMMDD.log /etc/fail2ban/filter.d/jellyfin.conf --print-all-matched
You should see matched lines with IP addresses highlighted.
Example:
| [2025-12-31 07:22:27.102 -07:00] [INF] Authentication request for "test@test" has been denied (IP: "140.32.72.74").
| [2025-12-31 07:22:28.787 -07:00] [INF] Authentication request for "test@test" has been denied (IP: "140.32.72.74").
| [2025-12-31 07:41:08.539 -07:00] [INF] Authentication request for "test@test" has been denied (IP: "140.32.72.74").
| [2025-12-31 07:41:10.368 -07:00] [INF] Authentication request for "test@test" has been denied (IP: "140.32.72.74").
Zero matches means your regex is wrong or your log path is incorrect.
Creating the Jellyfin Jail Configuration
Jails define what happens when the filter finds matches.
sudo nano /etc/fail2ban/jail.d/jellyfin.local
Base Configuration (Without a Reverse Proxy)
[jellyfin]
enabled = true
filter = jellyfin
logpath = /var/log/jellyfin/jellyfin*.log
backend = polling
maxretry = 5
findtime = 10m
bantime = 20m
port = 8096,8920
protocol = tcp
action = nftables[type=multiport]
ignoreip = 127.0.0.1/8 ::1
# Optional: add YOUR LAN subnet to ignoreip (recommended)
# Examples (pick the one that matches your network, or use the exact /24 you use)
# 10.0.0.0/8
# 172.16.0.0/12
# 192.168.0.0/16
That config means: 5 failures within 10 minutes earns a 20-minute ban. Tune to taste. Don’t make maxretry too low or bantime too long. You’ll lock yourself out the first time you mistype a password while trying to watch something away from home.
backend = polling is needed because Jellyfin creates a new log file every 24 hours with a unique name. This lets fail2ban read these files.That’s the basics of Fail2Ban when you’re exposing Jellyfin directly to the internet via port forwarding. I strongly recommend a reverse proxy rather than exposing Jellyfin directly.
I’ll cover the reverse-proxy setup in a follow-up post.

A spare Pi is a great place to break things before you touch the LXC running your real library. Once the jail behaves there, copy the configs over with confidence.
Monitoring and Maintenance
Watch Ban Activity
Want to see bans happen in real-time? This is oddly satisfying:
sudo tail -f /var/log/fail2ban.log
You’ll see IPs getting banned as they trip your thresholds. After the first few, you realize how many bots are constantly probing your server.
Unban Yourself
Locked yourself out? It happens. From the LXC console:
sudo fail2ban-client set jellyfin unbanip 192.168.1.50
Replace with your actual IP. You’ll be unbanned immediately.
Best Practices for Fail2Ban Management
- Keep
bantimefinite. Infinite bans sound appealing, but they’re risky. You can ban yourself permanently. - Test filters after every Jellyfin update. Log formats change.
- Review ban logs monthly for patterns. If you’re getting hammered from specific countries, consider additional firewall rules.
- Document your unban procedure somewhere you can access when locked out.
Troubleshooting Common Fail2Ban Issues
No bans occur: Wrong filter regex, wrong logpath, or Jellyfin’s logging level is too low. Use fail2ban-regex to debug. Also check that Fail2Ban is actually reading the log file. Permissions matter.
Locked out completely: Access your LXC console (not SSH, that’s blocked too), unban manually, then add your IP to ignoreip in the jail config.
FAQs
➤ Will Fail2Ban ban my own IP?
maxretry. Use ignoreip for your home network or keep the unban command handy. I’ve locked myself out more times than I’d like to admit.➤ What's the difference between bantime and findtime?
findtime is the detection window, how far back Fail2Ban looks for failures. bantime is how long the ban lasts. So with findtime = 600 and maxretry = 3, you get banned if you fail 3 times within 10 minutes. The ban then lasts for bantime seconds.➤ Does Fail2Ban work with nftables?
➤ Is Fail2Ban useful for local-only Jellyfin?
Conclusion: Harden Your Jellyfin Installation
If your Jellyfin instance is reachable from the internet, Fail2Ban is essential. Brute-force attempts are constant and invisible until you check your logs. I was shocked when I first looked. Thousands of attempts per day from IPs all over the world.
Understand your network topology, configure the correct jail and action, and monitor bans periodically. That’s how you turn Jellyfin from a soft target into a more secure service. This is the third step in hardening your Jellyfin LXC.
You should have already:
- Be using an unprivileged LXC - LXC Guide for Jellyfin
- Set Firewall Rules
- Secured your SSH - How to secure SSH
- Now Fail2Ban
I learned this by watching bad actors brute-force my server for days. Now they get five tries and a timeout. Yours should too.
Next steps:
- Pair Fail2Ban with HTTPS and strong passwords
- Add a reverse proxy like NGINX or Caddy
Resources


