Featured image of post How to Install and Configure Fail2Ban on your Jellyfin LXC

How to Install and Configure Fail2Ban on your Jellyfin LXC

Stop brute-force attacks on your Jellyfin server with Fail2Ban and not lock yourself out

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 immediately start stuffing passwords trying to guess their way in.

Ask me how I know.

I had the default username jellyfin when I first setup and exposed Jellyfin to the internet. Once I did this that the account was constantly being hammered by bots that found the login page. I found it because the account would get locked by Jellyfin and I couldn’t login to watch anything. I knew better but, here I was looking at thousands of failed login attempts from IPs spread across multiple countries.

I immediately changed the default username. This helped the account was no longer getting locked out. However, the Jellyfin server was still being assaulted by bots on a daily basis. So, I knew I needed an additional layer of security. Fail2Ban was the easy choice.

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.

💭
TL;DR:

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 it isn’t set on a user account an attacker can try thousands of passwords per hour without consequence.

Jellyfin's Lockout setting

Fail2Ban does this globally, and 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 (HTTPS), failed login attempts look the same in the logs. That’s actually good news. It means one Fail2Ban config works for both scenarios.

Pre-Deployment Checklist

Answer these before touching any of your 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 --version and sudo 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?
📝
Note:

I’ll be configuring this on an unprivileged LXC running Debian 13 with nftables.

So, all of my examples will be for this setup.

Checking Your Firewall Backend

Modern Debian systems often use 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.
Getting this wrong means 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 will survive 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
MINISFORUM MS-01 Mini Workstation: Why it fits this post: This mini workstation is ideal for running Jellyfin in an LXC container and experimenting with Fail…
MINISFORUM MS-01 Mini Workstation This mini workstation is ideal for running Jellyfin in an LXC container and experimenting with Fail2Ban, offering enough power and networking for a secure, flexible homelab setup.
Amazon Price: Loading...
Availability: Checking...
Contains affiliate links. I may earn a commission at no cost to you.

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 =
⚠️
Warning: Jellyfin log formats can change between versions. I learned this the hard way after an update when bans mysteriously stopped working. Always test your filter after upgrades.

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").

If you see zero matches, your regex is wrong or your log path is incorrect.

💡
Tip: Don’t skip this step. I’ve wasted hours debugging jails that would never work because the filters never matched anything.

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

This means: 5 failures within 10 minutes results in a 20-minute ban. Adjust these to taste, but don’t make maxretry too low or the bantime too long, you could lock yourself out when you mistype your password while trying to watch something away from home.

💡
Tip: backend = polling is needed because Jellyfin creates a new log file every 24 hours with a unique name. This allows fail2ban to read these files.

This is basics of Fail2Ban used when you are exposing your Jellyfin directly to the internet via port forwarding. I highly Recommend a Reverse Proxy rather than exposing Jellyfin directly to the internet.

I will cover more advanced setups like reverse proxies at a later date.

RaspberryPi 4GB: The Raspberry Pi is a budget-friendly, low-power option if you are wanting to test Fail2Ban and Jellyfin in a lightweight, isolated environment before deploying to larger servers.
RaspberryPi 4GB The Raspberry Pi is a budget-friendly, low-power option if you are wanting to test Fail2Ban and Jellyfin in a lightweight, isolated environment before deploying to larger servers.
Amazon Price: Loading...
Availability: Checking...
Contains affiliate links. I may earn a commission at no cost to you.

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 just 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 bantime finite. Infinite bans sound appealing, but they’re risky. You could 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?
Yes, if you exceed 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?
Yes, but verify the backend configuration matches your system. Modern Debian uses nftables but Fail2Ban might still default to iptables compatibility mode. Check your jail config.
➤ Is Fail2Ban useful for local-only Jellyfin?
Not really. It matters when Jellyfin is exposed beyond your LAN.

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.

By understanding your network topology, configuring the correct jail and action, and monitoring bans periodically, you turn Jellyfin from a soft target into a more secure service. This is just the third step in hardening your Jellyfin LXC.

You should have already:

I learned this by watching bad actors trying to 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

Protectli FW6C/FW6D: This dedicated firewall appliance can help you by implementing network-level protections and monitor traffic, complementing Fail2Ban's application-level security for a more robust homelab.
Protectli FW6C/FW6D This dedicated firewall appliance can help you by implementing network-level protections and monitor traffic, complementing Fail2Ban’s application-level security for a more robust homelab.
Amazon Price: Loading...
Availability: Checking...
Contains affiliate links. I may earn a commission at no cost to you.
© 2024 - 2026 DiyMediaServer

Buy Me a Coffee

Built with Hugo
Using a modified Theme Stack designed by Jimmy