Skip to content

Help with Fail2Ban setup #1220

@xfzv

Description

@xfzv

I've been trying to get Fail2Ban working (#847) but I'm not there yet.

I'm using NixOS with the following setup (in a VM for now):

{
  virtualisation.docker.enable = true;

  virtualisation.oci-containers = {
    backend = "docker";
    containers = {
      "linkding" = {
        autoStart = true;
        image = "sissbruecker/linkding:latest";

        environment = {
          LD_FAVICON_PROVIDER = "https://icons.duckduckgo.com/ip3/{domain}.ico";
          LD_SERVER_HOST = "0.0.0.0";
          LD_SERVER_PORT = "9090";
          LD_SUPERUSER_NAME = "redacted";
          LD_SUPERUSER_PASSWORD = "redacted";
        };

        ports = [ "9090:9090" ];
        volumes = [ "linkding_data:/etc/linkding/data" ];
      };
    };
  };

  services.fail2ban = {
    enable = true;
    jails = {
      linkding = {
        settings = {
          enabled = true;
          filter = "linkding";
          port = 9090;
          maxretry = 1;
          bantime = "2d";
          findtime = "1d";
          journalmatch = "_SYSTEMD_UNIT=docker-linkding.service";
        };
      };
    };
  };

  environment.etc."fail2ban/filter.d/linkding.conf".text =
    # ini
    ''
      [Definition]
      failregex = ^.*<HOST>.*() {.*} \[.*\]  POST  \/login\/.*(HTTP\/1.1 401)
      journalmatch = _SYSTEMD_UNIT=docker-linkding.service
    '';
}

fail2ban-regex returns a match when testing the regex:

% fail2ban-regex 'Oct 29 19:14:51 vm docker-linkding-start[2425]: [pid: 19|app: 0|req: 142/380] <redacted> () {48 vars in 850 bytes} [Wed Oct 29 15:16:42 2025]  POST  /login/ => generated 4937 bytes in 470 msecs (HTTP/1.1 401) 11 headers in 519 bytes (1 switches on core 0)' '^.*<HOST>.*() {.*} \[.*\]  POST  \/login\/.*(HTTP\/1.1 401)'

Running tests
=============

Use   failregex line : ^.*<HOST>.*() {.*} \[.*\]  POST  \/login\/.*(HTTP\...
Use      single line : Oct 29 19:14:51 vm docker-linkding-start[2425]: [p...


Results
=======

Failregex: 1 total
|-  #) [# of hits] regular expression
|   1) [1] ^.*<HOST>.*() {.*} \[.*\]  POST  \/login\/.*(HTTP\/1.1 401)
`-

Ignoreregex: 0 total

Date template hits:
|- [# of hits] date format
|  [1] {^LN-BEG}(?:DAY )?MON Day %k:Minute:Second(?:\.Microseconds)?(?: ExYear)?
`-

Lines: 1 lines, 0 ignored, 1 matched, 0 missed
[processed in 0.00 sec]

The linkding jail is active. However, if I simulate a fake login attempt from another machine, nothing shows up in fail2ban.service and the ban action isn't performed:

# fail2ban-client status linkding

Status for the jail: linkding
|- Filter
|  |- Currently failed:	0
|  |- Total failed:	0
|  `- Journal matches:	_SYSTEMD_UNIT=docker-linkding.service
`- Actions
   |- Currently banned:	0
   |- Total banned:	0
   `- Banned IP list:	

The built-in sshd jail works just fine:

# fail2ban-client status sshd
Status for the jail: sshd
|- Filter
|  |- Currently failed:	0
|  |- Total failed:	1
|  `- Journal matches:	_SYSTEMD_UNIT=sshd.service + _COMM=sshd
`- Actions
   |- Currently banned:	1
   |- Total banned:	1
   `- Banned IP list: 	 <redacted>

and I also have another custom jail for another service that also works, so I guess my Fail2Ban config for linkding is wrong.

Thanks in advance for any suggestion! It would be nice if this could be added to the docs, as requested in #847 (comment)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions