├── .github ├── CODEOWNERS ├── FUNDING.yml ├── docker-fail2ban.jpg ├── dependabot.yml ├── ISSUE_TEMPLATE │ ├── config.yml │ ├── feature.yml │ └── bug.yml ├── workflows │ ├── labels.yml │ ├── test.yml │ └── build.yml ├── labels.yml └── SUPPORT.md ├── .gitattributes ├── test ├── fail2ban.env └── compose.yml ├── examples ├── compose │ ├── fail2ban.env │ └── compose.yml ├── smtp │ ├── fail2ban.env │ ├── data │ │ └── jail.d │ │ │ └── 00-jail.local │ ├── msmtpd.env │ └── compose.yml └── jails │ ├── proxmox │ ├── filter.d │ │ └── proxmox-auth.conf │ └── jail.d │ │ └── proxmox.conf │ ├── guacamole │ ├── filter.d │ │ └── guacamole-auth.conf │ ├── jail.d │ │ └── guacamole.conf │ └── README.md │ ├── sshd │ ├── jail.d │ │ └── sshd.conf │ └── README.md │ └── traefik │ ├── filter.d │ └── traefik-botsearch.conf │ ├── jail.d │ └── traefik.conf │ └── README.md ├── .editorconfig ├── docker-bake.hcl ├── LICENSE ├── Dockerfile ├── entrypoint.sh ├── CHANGELOG.md └── README.md /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @crazy-max 2 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | /*.sh linguist-detectable=false 2 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: crazy-max 2 | custom: https://www.paypal.me/crazyws 3 | -------------------------------------------------------------------------------- /.github/docker-fail2ban.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crazy-max/docker-fail2ban/HEAD/.github/docker-fail2ban.jpg -------------------------------------------------------------------------------- /test/fail2ban.env: -------------------------------------------------------------------------------- 1 | TZ=Europe/Paris 2 | 3 | F2B_LOG_TARGET=STDOUT 4 | F2B_LOG_LEVEL=INFO 5 | F2B_DB_PURGE_AGE=1d 6 | -------------------------------------------------------------------------------- /examples/compose/fail2ban.env: -------------------------------------------------------------------------------- 1 | TZ=Europe/Paris 2 | 3 | F2B_LOG_TARGET=STDOUT 4 | F2B_LOG_LEVEL=INFO 5 | F2B_DB_PURGE_AGE=1d 6 | -------------------------------------------------------------------------------- /examples/smtp/fail2ban.env: -------------------------------------------------------------------------------- 1 | TZ=Europe/Paris 2 | 3 | F2B_LOG_TARGET=STDOUT 4 | F2B_LOG_LEVEL=INFO 5 | F2B_DB_PURGE_AGE=1d 6 | -------------------------------------------------------------------------------- /examples/smtp/data/jail.d/00-jail.local: -------------------------------------------------------------------------------- 1 | [DEFAULT] 2 | action = smtp.py[host=localhost:2500, sendername=Fail2Ban, sender=foo@gmail.com, dest=foo@gmail.com] 3 | -------------------------------------------------------------------------------- /examples/jails/proxmox/filter.d/proxmox-auth.conf: -------------------------------------------------------------------------------- 1 | [Definition] 2 | failregex = pvedaemon\[.*authentication failure; rhost= user=.* msg=.* 3 | ignoreregex = 4 | -------------------------------------------------------------------------------- /examples/jails/guacamole/filter.d/guacamole-auth.conf: -------------------------------------------------------------------------------- 1 | [Definition] 2 | failregex = \bAuthentication attempt from [(?:,.*)?] for user ".*" failed\. 3 | ignoreregex = 4 | -------------------------------------------------------------------------------- /examples/jails/sshd/jail.d/sshd.conf: -------------------------------------------------------------------------------- 1 | [sshd] 2 | enabled = true 3 | chain = INPUT 4 | port = ssh 5 | filter = sshd[mode=aggressive] 6 | logpath = /var/log/auth.log 7 | maxretry = 5 8 | -------------------------------------------------------------------------------- /examples/jails/traefik/filter.d/traefik-botsearch.conf: -------------------------------------------------------------------------------- 1 | [INCLUDES] 2 | before = botsearch-common.conf 3 | 4 | [Definition] 5 | failregex = ^ \- \S+ \[\] \"(GET|POST|HEAD) \/ \S+\" 404 .+$ 6 | -------------------------------------------------------------------------------- /examples/jails/proxmox/jail.d/proxmox.conf: -------------------------------------------------------------------------------- 1 | [proxmox-auth] 2 | enabled = true 3 | chain = INPUT 4 | port = https,http,8006 5 | filter = proxmox-auth 6 | logpath = /var/log/daemon.log 7 | maxretry = 3 8 | bantime = 3600 9 | -------------------------------------------------------------------------------- /examples/jails/guacamole/jail.d/guacamole.conf: -------------------------------------------------------------------------------- 1 | [guacamole-auth] 2 | enabled = true 3 | chain = DOCKER-USER 4 | port = http,https 5 | filter = guacamole-auth 6 | logpath = /var/log/guacamole/guacd.log 7 | bantime = -1 8 | maxretry = 5 9 | -------------------------------------------------------------------------------- /examples/smtp/msmtpd.env: -------------------------------------------------------------------------------- 1 | # https://github.com/crazy-max/docker-msmtpd 2 | SMTP_HOST=smtp.gmail.com 3 | SMTP_PORT=587 4 | SMTP_TLS=on 5 | SMTP_STARTTLS=on 6 | SMTP_TLS_CHECKCERT=on 7 | SMTP_AUTH=on 8 | SMTP_USER=foo 9 | SMTP_PASSWORD=bar 10 | SMTP_FROM=foo@gmail.com 11 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "daily" 7 | time: "08:00" 8 | timezone: "Europe/Paris" 9 | labels: 10 | - "kind/dependencies" 11 | - "bot" 12 | -------------------------------------------------------------------------------- /examples/compose/compose.yml: -------------------------------------------------------------------------------- 1 | name: fail2ban 2 | 3 | services: 4 | fail2ban: 5 | image: crazymax/fail2ban:latest 6 | container_name: fail2ban 7 | network_mode: "host" 8 | cap_add: 9 | - NET_ADMIN 10 | - NET_RAW 11 | volumes: 12 | - "./data:/data" 13 | - "/var/log:/var/log:ro" 14 | env_file: 15 | - "./fail2ban.env" 16 | restart: always 17 | -------------------------------------------------------------------------------- /examples/jails/traefik/jail.d/traefik.conf: -------------------------------------------------------------------------------- 1 | [traefik-auth] 2 | enabled = true 3 | chain = DOCKER-USER 4 | port = http,https 5 | filter = traefik-auth 6 | mode = aggressive 7 | logpath = /var/log/traefik/access.log 8 | 9 | [traefik-auth-ddos] 10 | filter = traefik-auth[mode=ddos] 11 | 12 | [traefik-botsearch] 13 | enabled = true 14 | chain = DOCKER-USER 15 | port = http,https 16 | filter = traefik-botsearch 17 | logpath = /var/log/traefik/access.log 18 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | # https://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/configuring-issue-templates-for-your-repository#configuring-the-template-chooser 2 | blank_issues_enabled: true 3 | contact_links: 4 | - name: Questions and Discussions 5 | url: https://github.com/crazy-max/docker-fail2ban/discussions/new 6 | about: Use Github Discussions to ask questions and/or open discussion topics. 7 | -------------------------------------------------------------------------------- /test/compose.yml: -------------------------------------------------------------------------------- 1 | name: fail2ban 2 | 3 | services: 4 | fail2ban: 5 | image: ${FAIL2BAN_IMAGE:-crazymax/fail2ban} 6 | container_name: ${FAIL2BAN_CONTAINER:-fail2ban} 7 | network_mode: host 8 | cap_add: 9 | - NET_ADMIN 10 | - NET_RAW 11 | volumes: 12 | - "./data:/data" 13 | - "/var/log:/var/log:ro" 14 | env_file: 15 | - "./fail2ban.env" 16 | environment: 17 | - "IPTABLES_MODE=${IPTABLES_MODE:-auto}" 18 | restart: always 19 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature.yml: -------------------------------------------------------------------------------- 1 | # https://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/syntax-for-githubs-form-schema 2 | name: Feature request 3 | description: Missing functionality? Come tell us about it! 4 | labels: 5 | - kind/enhancement 6 | - status/triage 7 | 8 | body: 9 | - type: textarea 10 | id: description 11 | attributes: 12 | label: Description 13 | description: What is the feature you want to see? 14 | validations: 15 | required: true 16 | -------------------------------------------------------------------------------- /examples/smtp/compose.yml: -------------------------------------------------------------------------------- 1 | name: fail2ban 2 | 3 | services: 4 | msmtpd: 5 | image: crazymax/msmtpd:latest 6 | container_name: fail2ban_msmtpd 7 | env_file: 8 | - "./msmtpd.env" 9 | ports: 10 | - "127.0.0.1:2500:2500" 11 | environment: 12 | - "TZ" 13 | restart: always 14 | 15 | fail2ban: 16 | image: crazymax/fail2ban:latest 17 | container_name: fail2ban 18 | depends_on: 19 | msmtpd: 20 | condition: service_healthy 21 | required: true 22 | network_mode: "host" 23 | cap_add: 24 | - NET_ADMIN 25 | - NET_RAW 26 | volumes: 27 | - "./data:/data" 28 | - "/var/log:/var/log:ro" 29 | env_file: 30 | - "./fail2ban.env" 31 | restart: always 32 | -------------------------------------------------------------------------------- /docker-bake.hcl: -------------------------------------------------------------------------------- 1 | variable "DEFAULT_TAG" { 2 | default = "fail2ban:local" 3 | } 4 | 5 | // Special target: https://github.com/docker/metadata-action#bake-definition 6 | target "docker-metadata-action" { 7 | tags = ["${DEFAULT_TAG}"] 8 | } 9 | 10 | // Default target if none specified 11 | group "default" { 12 | targets = ["image-local"] 13 | } 14 | 15 | target "image" { 16 | inherits = ["docker-metadata-action"] 17 | } 18 | 19 | target "image-local" { 20 | inherits = ["image"] 21 | output = ["type=docker"] 22 | } 23 | 24 | target "image-all" { 25 | inherits = ["image"] 26 | platforms = [ 27 | "linux/386", 28 | "linux/amd64", 29 | "linux/arm/v6", 30 | "linux/arm/v7", 31 | "linux/arm64", 32 | "linux/ppc64le", 33 | "linux/riscv64", 34 | "linux/s390x" 35 | ] 36 | } 37 | -------------------------------------------------------------------------------- /.github/workflows/labels.yml: -------------------------------------------------------------------------------- 1 | name: labels 2 | 3 | concurrency: 4 | group: ${{ github.workflow }}-${{ github.ref }} 5 | cancel-in-progress: true 6 | 7 | # https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#permissions 8 | permissions: 9 | contents: read 10 | 11 | on: 12 | push: 13 | branches: 14 | - 'master' 15 | paths: 16 | - '.github/labels.yml' 17 | - '.github/workflows/labels.yml' 18 | pull_request: 19 | paths: 20 | - '.github/labels.yml' 21 | - '.github/workflows/labels.yml' 22 | 23 | jobs: 24 | labeler: 25 | runs-on: ubuntu-latest 26 | permissions: 27 | # same as global permissions 28 | contents: read 29 | # required to update labels 30 | issues: write 31 | steps: 32 | - 33 | name: Checkout 34 | uses: actions/checkout@v6 35 | - 36 | name: Run Labeler 37 | uses: crazy-max/ghaction-github-labeler@v5 38 | with: 39 | dry-run: ${{ github.event_name == 'pull_request' }} 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018-2025 CrazyMax 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # syntax=docker/dockerfile:1 2 | 3 | ARG FAIL2BAN_VERSION=1.1.0 4 | ARG ALPINE_VERSION=3.22 5 | 6 | FROM --platform=$BUILDPLATFORM scratch AS src 7 | ARG FAIL2BAN_VERSION 8 | ADD "https://github.com/fail2ban/fail2ban.git#${FAIL2BAN_VERSION}" . 9 | 10 | FROM alpine:${ALPINE_VERSION} 11 | RUN --mount=from=src,target=/tmp/fail2ban,rw \ 12 | apk --update --no-cache add \ 13 | bash \ 14 | curl \ 15 | grep \ 16 | ipset \ 17 | iptables \ 18 | iptables-legacy \ 19 | kmod \ 20 | nftables \ 21 | openssh-client-default \ 22 | python3 \ 23 | py3-dnspython \ 24 | py3-inotify \ 25 | tzdata \ 26 | wget \ 27 | whois \ 28 | && apk --update --no-cache add -t build-dependencies \ 29 | build-base \ 30 | py3-pip \ 31 | py3-setuptools \ 32 | python3-dev \ 33 | && cd /tmp/fail2ban \ 34 | && 2to3 -w --no-diffs bin/* fail2ban \ 35 | && python3 setup.py install --without-tests \ 36 | && apk del build-dependencies \ 37 | && rm -rf /etc/fail2ban/jail.d /root/.cache 38 | 39 | COPY entrypoint.sh /entrypoint.sh 40 | 41 | ENV TZ="UTC" 42 | 43 | VOLUME [ "/data" ] 44 | 45 | ENTRYPOINT [ "/entrypoint.sh" ] 46 | CMD [ "fail2ban-server", "-f", "-x", "-v", "start" ] 47 | 48 | HEALTHCHECK --interval=10s --timeout=5s \ 49 | CMD fail2ban-client ping || exit 1 50 | -------------------------------------------------------------------------------- /.github/labels.yml: -------------------------------------------------------------------------------- 1 | ## more info https://github.com/crazy-max/ghaction-github-labeler 2 | - 3 | name: "bot" 4 | color: "69cde9" 5 | description: "" 6 | - 7 | name: "good first issue" 8 | color: "7057ff" 9 | description: "" 10 | - 11 | name: "help wanted" 12 | color: "4caf50" 13 | description: "" 14 | - 15 | name: "area/ci" 16 | color: "ed9ca9" 17 | description: "" 18 | - 19 | name: "area/dockerfile" 20 | color: "03a9f4" 21 | description: "" 22 | - 23 | name: "kind/bug" 24 | color: "b60205" 25 | description: "" 26 | - 27 | name: "kind/dependencies" 28 | color: "0366d6" 29 | description: "" 30 | - 31 | name: "kind/docs" 32 | color: "c5def5" 33 | description: "" 34 | - 35 | name: "kind/duplicate" 36 | color: "cccccc" 37 | description: "" 38 | - 39 | name: "kind/enhancement" 40 | color: "0054ca" 41 | description: "" 42 | - 43 | name: "kind/invalid" 44 | color: "e6e6e6" 45 | description: "" 46 | - 47 | name: "kind/upstream" 48 | color: "fbca04" 49 | description: "" 50 | - 51 | name: "kind/wontfix" 52 | color: "ffffff" 53 | description: "" 54 | - 55 | name: "status/automerge" 56 | color: "8f4fbc" 57 | description: "" 58 | - 59 | name: "status/needs-investigation" 60 | color: "e6625b" 61 | description: "" 62 | - 63 | name: "status/needs-more-info" 64 | color: "795548" 65 | description: "" 66 | - 67 | name: "status/stale" 68 | color: "237da0" 69 | description: "" 70 | - 71 | name: "status/triage" 72 | color: "dde4b7" 73 | description: "" 74 | -------------------------------------------------------------------------------- /examples/jails/guacamole/README.md: -------------------------------------------------------------------------------- 1 | ## Guacamole 2 | 3 | Create the logback configuration in `./config/guacamole/logback.xml`: 4 | 5 | ```xml 6 | 7 | 8 | 9 | 10 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 11 | 12 | 13 | 14 | 15 | /usr/local/tomcat/logs/guacd.log 16 | 17 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | ``` 27 | 28 | Create this compose file for guacamole: 29 | 30 | ```yaml 31 | services: 32 | guacamole: 33 | image: oznu/guacamole 34 | volumes: 35 | - ./config:/config 36 | - /var/log/guacamole:/usr/local/tomcat/logs 37 | ports: 38 | - target: 8080 39 | published: 8080 40 | protocol: tcp 41 | ``` 42 | 43 | Guacamole will write logs into `/usr/local/tomcat/logs` and bind the folder 44 | to `/var/log/guacamole` on the host. 45 | 46 | ## Fail2ban container 47 | 48 | * Copy files from [filter.d](filter.d) and [jail.d](jail.d) to `./data` in 49 | their respective folders. 50 | -------------------------------------------------------------------------------- /.github/SUPPORT.md: -------------------------------------------------------------------------------- 1 | # Support [![](https://isitmaintained.com/badge/resolution/crazy-max/docker-fail2ban.svg)](https://isitmaintained.com/project/crazy-max/docker-fail2ban) 2 | 3 | ## Reporting an issue 4 | 5 | Please do a search in [open issues](https://github.com/crazy-max/docker-fail2ban/issues?utf8=%E2%9C%93&q=) to see if the issue or feature request has already been filed. 6 | 7 | If you find your issue already exists, make relevant comments and add your [reaction](https://github.com/blog/2119-add-reactions-to-pull-requests-issues-and-comments). Use a reaction in place of a "+1" comment. 8 | 9 | :+1: - upvote 10 | 11 | :-1: - downvote 12 | 13 | If you cannot find an existing issue that describes your bug or feature, submit an issue using the guidelines below. 14 | 15 | ## Writing good bug reports and feature requests 16 | 17 | File a single issue per problem and feature request. 18 | 19 | * Do not enumerate multiple bugs or feature requests in the same issue. 20 | * Do not add your issue as a comment to an existing issue unless it's for the identical input. Many issues look similar, but have different causes. 21 | 22 | The more information you can provide, the more likely someone will be successful reproducing the issue and finding a fix. 23 | 24 | You are now ready to [create a new issue](https://github.com/crazy-max/docker-fail2ban/issues/new/choose)! 25 | 26 | ## Closure policy 27 | 28 | * Support directly related to Fail2ban will not be provided if your problem is not related to the operation of this image. 29 | * Issues that don't have the information requested above (when applicable) will be closed immediately and the poster directed to the support guidelines. 30 | * Issues that go a week without a response from original poster are subject to closure at my discretion. 31 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | concurrency: 4 | group: ${{ github.workflow }}-${{ github.ref }} 5 | cancel-in-progress: true 6 | 7 | # https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#permissions 8 | permissions: 9 | contents: read 10 | 11 | on: 12 | push: 13 | branches: 14 | - 'master' 15 | paths-ignore: 16 | - '**.md' 17 | pull_request: 18 | 19 | 20 | env: 21 | BUILD_TAG: fail2ban:test 22 | CONTAINER_NAME: fail2ban 23 | 24 | jobs: 25 | test: 26 | runs-on: ubuntu-latest 27 | strategy: 28 | fail-fast: false 29 | matrix: 30 | iptables: 31 | - auto 32 | - nft 33 | - legacy 34 | steps: 35 | - 36 | name: Checkout 37 | uses: actions/checkout@v6 38 | - 39 | name: Set up QEMU 40 | uses: docker/setup-qemu-action@v3 41 | - 42 | name: Set up Docker Buildx 43 | uses: docker/setup-buildx-action@v3 44 | - 45 | name: Build 46 | uses: docker/bake-action@v6 47 | with: 48 | source: . 49 | targets: image-local 50 | env: 51 | DEFAULT_TAG: ${{ env.BUILD_TAG }} 52 | - 53 | name: Start 54 | run: | 55 | docker compose up -d 56 | working-directory: test 57 | env: 58 | FAIL2BAN_IMAGE: ${{ env.BUILD_TAG }} 59 | FAIL2BAN_CONTAINER: ${{ env.CONTAINER_NAME }} 60 | IPTABLES_MODE: ${{ matrix.iptables }} 61 | - 62 | name: Check container logs 63 | uses: crazy-max/.github/.github/actions/container-logs-check@main 64 | with: 65 | container_name: ${{ env.CONTAINER_NAME }} 66 | log_check: "Server ready" 67 | timeout: 120 68 | - 69 | name: Logs 70 | if: always() 71 | run: | 72 | docker compose logs 73 | working-directory: test 74 | env: 75 | FAIL2BAN_IMAGE: ${{ env.BUILD_TAG }} 76 | FAIL2BAN_CONTAINER: ${{ env.CONTAINER_NAME }} 77 | -------------------------------------------------------------------------------- /examples/jails/traefik/README.md: -------------------------------------------------------------------------------- 1 | ## Traefik 2 | 3 | If you want to block IPs that have HTTP Basic Authentication failures on 4 | [Traefik](https://traefik.io/), and ban them with iptables, read the 5 | instructions below. 6 | 7 | First you have to configure your Traefik instance to write the [access logs](https://doc.traefik.io/traefik/observability/access-logs/) 8 | into a log file on host and specifiy users for [Basic Authentication](https://doc.traefik.io/traefik/middlewares/http/basicauth/). 9 | You can use the following compose as a quick example: 10 | 11 | ```yml 12 | services: 13 | traefik: 14 | image: traefik:2.8-alpine 15 | command: 16 | - "--log" 17 | - "--log.level=INFO" 18 | - "--accesslog" 19 | - "--accesslog.filepath=/var/log/access.log" 20 | - "--accesslog.filters.statuscodes=400-499" 21 | - "--entrypoints.http.address=:80" 22 | - "--entrypoints.https.address=:443" 23 | - "--entrypoints.https.tls=true" 24 | - "--providers.docker=true" 25 | - "--providers.docker.watch" 26 | - "--api" 27 | - "--api.dashboard" 28 | ports: 29 | - target: 80 30 | published: 80 31 | protocol: tcp 32 | - target: 443 33 | published: 443 34 | protocol: tcp 35 | labels: 36 | - "traefik.enable=true" 37 | - "traefik.http.routers.traefik.entrypoints=https" 38 | - "traefik.http.routers.traefik.rule=Host(`traefik.example.com`)" 39 | - "traefik.http.routers.traefik.service=api@internal" 40 | - "traefik.http.routers.traefik.middlewares=traefik-auth" 41 | - "traefik.http.routers.traefik.tls=true" 42 | - "traefik.http.middlewares.traefik-auth.basicauth.usersfile=/htpasswd" 43 | volumes: 44 | - "./htpasswd:/htpasswd" 45 | - "/var/log/traefik:/var/log" 46 | - "/var/run/docker.sock:/var/run/docker.sock" 47 | restart: always 48 | ``` 49 | 50 | Traefik will write logs into `/var/log/access.log` for HTTP status code 51 | `400-499` and bind the folder to `/var/log/traefik` on the host. 52 | 53 | It will also create a [Basic Authentication](https://doc.traefik.io/traefik/middlewares/http/basicauth/) 54 | mechanism through `/htpasswd` file. You can populate this file with the 55 | following command: 56 | 57 | ```console 58 | $ docker pull httpd:2.4-alpine 59 | $ docker run --rm httpd:2.4-alpine htpasswd -nbB >> ./htpasswd 60 | ``` 61 | 62 | ## Fail2ban container 63 | 64 | * Copy files from [filter.d](filter.d) and [jail.d](jail.d) to `./data` in 65 | * their respective folders 66 | -------------------------------------------------------------------------------- /entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | TZ=${TZ:-UTC} 4 | 5 | F2B_LOG_TARGET=${F2B_LOG_TARGET:-STDOUT} 6 | F2B_LOG_LEVEL=${F2B_LOG_LEVEL:-INFO} 7 | F2B_DB_PURGE_AGE=${F2B_DB_PURGE_AGE:-1d} 8 | IPTABLES_MODE=${IPTABLES_MODE:-auto} 9 | 10 | # Timezone 11 | echo "Setting timezone to ${TZ}..." 12 | ln -snf /usr/share/zoneinfo/${TZ} /etc/localtime 13 | echo ${TZ} > /etc/timezone 14 | 15 | # Init 16 | echo "Initializing files and folders..." 17 | mkdir -p /data/db /data/action.d /data/filter.d /data/jail.d 18 | ln -sf /data/jail.d /etc/fail2ban/ 19 | 20 | # Fail2ban conf 21 | echo "Setting Fail2ban configuration..." 22 | sed -i "s|logtarget =.*|logtarget = $F2B_LOG_TARGET|g" /etc/fail2ban/fail2ban.conf 23 | sed -i "s/loglevel =.*/loglevel = $F2B_LOG_LEVEL/g" /etc/fail2ban/fail2ban.conf 24 | sed -i "s/dbfile =.*/dbfile = \/data\/db\/fail2ban\.sqlite3/g" /etc/fail2ban/fail2ban.conf 25 | sed -i "s/dbpurgeage =.*/dbpurgeage = $F2B_DB_PURGE_AGE/g" /etc/fail2ban/fail2ban.conf 26 | sed -i "s/#allowipv6 =.*/allowipv6 = auto/g" /etc/fail2ban/fail2ban.conf 27 | 28 | # Check custom actions 29 | echo "Checking for custom actions in /data/action.d..." 30 | actions=$(ls -l /data/action.d | grep -E '^-' | awk '{print $9}') 31 | for action in ${actions}; do 32 | if [ -f "/etc/fail2ban/action.d/${action}" ]; then 33 | echo " WARNING: ${action} already exists and will be overriden" 34 | rm -f "/etc/fail2ban/action.d/${action}" 35 | fi 36 | echo " Add custom action ${action}..." 37 | ln -sf "/data/action.d/${action}" "/etc/fail2ban/action.d/" 38 | done 39 | 40 | # Check custom filters 41 | echo "Checking for custom filters in /data/filter.d..." 42 | filters=$(ls -l /data/filter.d | grep -E '^-' | awk '{print $9}') 43 | for filter in ${filters}; do 44 | if [ -f "/etc/fail2ban/filter.d/${filter}" ]; then 45 | echo " WARNING: ${filter} already exists and will be overriden" 46 | rm -f "/etc/fail2ban/filter.d/${filter}" 47 | fi 48 | echo " Add custom filter ${filter}..." 49 | ln -sf "/data/filter.d/${filter}" "/etc/fail2ban/filter.d/" 50 | done 51 | 52 | iptablesLegacy=0 53 | if [ "$IPTABLES_MODE" = "auto" ] && ! iptables -L &> /dev/null; then 54 | echo "WARNING: iptables-nft is not supported by the host, falling back to iptables-legacy" 55 | iptablesLegacy=1 56 | elif [ "$IPTABLES_MODE" = "legacy" ]; then 57 | echo "WARNING: iptables-legacy enforced" 58 | iptablesLegacy=1 59 | fi 60 | if [ "$iptablesLegacy" -eq 1 ]; then 61 | ln -sf /usr/sbin/xtables-legacy-multi /usr/sbin/iptables 62 | ln -sf /usr/sbin/xtables-legacy-multi /usr/sbin/iptables-save 63 | ln -sf /usr/sbin/xtables-legacy-multi /usr/sbin/iptables-restore 64 | ln -sf /usr/sbin/xtables-legacy-multi /usr/sbin/ip6tables 65 | ln -sf /usr/sbin/xtables-legacy-multi /usr/sbin/ip6tables-save 66 | ln -sf /usr/sbin/xtables-legacy-multi /usr/sbin/ip6tables-restore 67 | fi 68 | 69 | iptables -V 70 | nft -v 71 | 72 | exec "$@" 73 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug.yml: -------------------------------------------------------------------------------- 1 | # https://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/syntax-for-githubs-form-schema 2 | name: Bug Report 3 | description: Report a bug 4 | labels: 5 | - kind/bug 6 | - status/triage 7 | 8 | body: 9 | - type: checkboxes 10 | attributes: 11 | label: Support guidelines 12 | description: Please read the support guidelines before proceeding. 13 | options: 14 | - label: I've read the [support guidelines](https://github.com/crazy-max/docker-fail2ban/blob/master/.github/SUPPORT.md) 15 | required: true 16 | 17 | - type: checkboxes 18 | attributes: 19 | label: I've found a bug and checked that ... 20 | description: | 21 | Make sure that your request fulfills all of the following requirements. If one requirement cannot be satisfied, explain in detail why. 22 | options: 23 | - label: ... the documentation does not mention anything about my problem 24 | - label: ... there are no open or closed issues that are related to my problem 25 | 26 | - type: textarea 27 | attributes: 28 | label: Description 29 | description: | 30 | Please provide a brief description of the bug in 1-2 sentences. 31 | validations: 32 | required: true 33 | 34 | - type: textarea 35 | attributes: 36 | label: Expected behaviour 37 | description: | 38 | Please describe precisely what you'd expect to happen. 39 | validations: 40 | required: true 41 | 42 | - type: textarea 43 | attributes: 44 | label: Actual behaviour 45 | description: | 46 | Please describe precisely what is actually happening. 47 | validations: 48 | required: true 49 | 50 | - type: textarea 51 | attributes: 52 | label: Steps to reproduce 53 | description: | 54 | Please describe the steps to reproduce the bug. 55 | placeholder: | 56 | 1. ... 57 | 2. ... 58 | 3. ... 59 | validations: 60 | required: true 61 | 62 | - type: textarea 63 | attributes: 64 | label: Docker info 65 | description: | 66 | Output of `docker info` command. 67 | render: text 68 | validations: 69 | required: true 70 | 71 | - type: textarea 72 | attributes: 73 | label: Docker Compose config 74 | description: | 75 | Output of `docker compose config` command. 76 | render: yaml 77 | 78 | - type: textarea 79 | attributes: 80 | label: Logs 81 | description: | 82 | Please provide the container logs (set `LOG_LEVEL=debug` if applicable). 83 | render: text 84 | validations: 85 | required: true 86 | 87 | - type: textarea 88 | attributes: 89 | label: Additional info 90 | description: | 91 | Please provide any additional information that seem useful. 92 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | concurrency: 4 | group: ${{ github.workflow }}-${{ github.ref }} 5 | cancel-in-progress: true 6 | 7 | # https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#permissions 8 | permissions: 9 | contents: read 10 | 11 | on: 12 | push: 13 | branches: 14 | - 'master' 15 | tags: 16 | - '*' 17 | paths-ignore: 18 | - '**.md' 19 | pull_request: 20 | 21 | env: 22 | DOCKERHUB_SLUG: crazymax/fail2ban 23 | GHCR_SLUG: ghcr.io/crazy-max/fail2ban 24 | 25 | jobs: 26 | build: 27 | runs-on: ubuntu-latest 28 | permissions: 29 | # same as global permissions 30 | contents: read 31 | # required to push to GHCR 32 | packages: write 33 | steps: 34 | - 35 | name: Docker meta 36 | id: meta 37 | uses: docker/metadata-action@v5 38 | with: 39 | images: | 40 | ${{ env.DOCKERHUB_SLUG }} 41 | ${{ env.GHCR_SLUG }} 42 | tags: | 43 | type=match,pattern=(.*)-r,group=1 44 | type=ref,event=pr 45 | type=edge 46 | labels: | 47 | org.opencontainers.image.title=Fail2ban 48 | org.opencontainers.image.description=Intrusion prevention software framework that protects computer servers from brute-force attacks 49 | org.opencontainers.image.vendor=CrazyMax 50 | - 51 | name: Set up QEMU 52 | uses: docker/setup-qemu-action@v3 53 | - 54 | name: Set up Docker Buildx 55 | uses: docker/setup-buildx-action@v3 56 | - 57 | name: Login to DockerHub 58 | if: github.event_name != 'pull_request' 59 | uses: docker/login-action@v3 60 | with: 61 | username: ${{ secrets.DOCKER_USERNAME }} 62 | password: ${{ secrets.DOCKER_PASSWORD }} 63 | - 64 | name: Login to GHCR 65 | if: github.event_name != 'pull_request' 66 | uses: docker/login-action@v3 67 | with: 68 | registry: ghcr.io 69 | username: ${{ github.repository_owner }} 70 | password: ${{ secrets.GITHUB_TOKEN }} 71 | - 72 | name: Build 73 | uses: docker/bake-action@v6 74 | with: 75 | files: | 76 | ./docker-bake.hcl 77 | cwd://${{ steps.meta.outputs.bake-file }} 78 | targets: image-all 79 | push: ${{ github.event_name != 'pull_request' }} 80 | - 81 | name: Check manifest 82 | if: github.event_name != 'pull_request' 83 | run: | 84 | docker buildx imagetools inspect ${{ env.DOCKERHUB_SLUG }}:${{ steps.meta.outputs.version }} 85 | docker buildx imagetools inspect ${{ env.GHCR_SLUG }}:${{ steps.meta.outputs.version }} 86 | - 87 | name: Inspect image 88 | if: github.event_name != 'pull_request' 89 | run: | 90 | docker pull ${{ env.DOCKERHUB_SLUG }}:${{ steps.meta.outputs.version }} 91 | docker image inspect ${{ env.DOCKERHUB_SLUG }}:${{ steps.meta.outputs.version }} 92 | docker pull ${{ env.GHCR_SLUG }}:${{ steps.meta.outputs.version }} 93 | docker image inspect ${{ env.GHCR_SLUG }}:${{ steps.meta.outputs.version }} 94 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 1.1.0-r1 (2024/05/09) 4 | 5 | * Support iptables-legacy for old kernels (#165) 6 | * Fallback to iptables-legacy if host doesn't support nft. Mode can be enforced 7 | with `IPTABLES_MODE` env var (#167) 8 | 9 | ## 1.1.0-r0 (2024/05/01) 10 | 11 | * Fail2ban 1.1.0 (#162) 12 | * Alpine Linux 3.19 (#163) 13 | 14 | ## 1.0.2-r1 (2023/08/29) 15 | 16 | * Alpine Linux 3.18 (#149) 17 | 18 | ## 1.0.2-r0 (2022/12/29) 19 | 20 | * Fail2ban 1.0.2 (#132) 21 | * Alpine Linux 3.17 (#135) 22 | 23 | ## 1.0.1-r0 (2022/10/06) 24 | 25 | * Fail2ban 1.0.1 (#130) 26 | * Alpine Linux 3.16 (#126) 27 | 28 | ## 0.11.2-r4 (2022/01/11) 29 | 30 | * Add SSH client (#110) 31 | * Alpine Linux 3.15 (#109) 32 | 33 | ## 0.11.2-r3 (2021/09/25) 34 | 35 | * Add `grep` (#102) 36 | 37 | ## 0.11.2-r2 (2021/08/19) 38 | 39 | * Alpine Linux 3.14 (#100) 40 | * Switch to buildx bake (#90) 41 | 42 | ## 0.11.2-RC1 (2020/12/23) 43 | 44 | * Fail2ban 0.11.2 45 | 46 | ## 0.11.1-RC5 (2020/11/22) 47 | 48 | * Rebuild to fix tzdata issue with Alpine 49 | 50 | ## 0.11.1-RC4 (2020/11/22) 51 | 52 | * Add `SSMTP_PASSWORD_FILE` env var 53 | 54 | ## 0.11.1-RC3 (2020/07/30) 55 | 56 | * Bringing the `INPUT` and `DOCKER-USER` chains together (#17 #46) 57 | * Remove `F2B_IPTABLES_CHAIN` env var 58 | * Update jails examples 59 | * Alpine Linux 3.12 60 | 61 | > :warning: **UPGRADE NOTES** 62 | > `F2B_IPTABLES_CHAIN` env var have been removed. 63 | > You must now define the targeted chain in your jail definition. 64 | > See [README](README.md#docker-user-and-input-chains) for more info. 65 | 66 | ## 0.11.1-RC2 (2020/03/22) 67 | 68 | * SSMTP: Add support for non-STARTTLS connections (#38) 69 | 70 | ## 0.11.1-RC1 (2020/01/18) 71 | 72 | * Fail2ban 0.11.1 73 | 74 | > :warning: **UPGRADE NOTES** 75 | > `F2B_BACKEND`, `F2B_MAX_RETRY`, `F2B_MAX_MATCHES`, `F2B_DEST_EMAIL`, `F2B_SENDER`, `F2B_ACTION` env vars have been removed. 76 | > You must now use them through the global jail configuration. 77 | > See [README](README.md#global-jail-configuration) for more info. 78 | 79 | ## 0.10.5-RC1 (2020/01/17) 80 | 81 | * Fail2ban 0.10.5 82 | * Add nftables support 83 | * Add `F2B_MAX_MATCHES` env var 84 | * Alpine Linux 3.11 85 | 86 | ## 0.10.4-RC13 (2019/12/07) 87 | 88 | * Fix timezone 89 | 90 | ## 0.10.4-RC12 (2019/10/03) 91 | 92 | * Multi-platform Docker image 93 | * Switch to GitHub Actions 94 | * :warning: Stop publishing Docker image on Quay 95 | * Set timezone through tzdata 96 | 97 | ## 0.10.4-RC11 (2019/09/16) 98 | 99 | * Only populate AuthUser/Pass in ssmtp.conf if defined in ENV (PR #28) 100 | 101 | ## 0.10.4-RC10 (2019/08/13) 102 | 103 | * Add option `F2B_BACKEND` to change default backend 104 | * Add dnspython3 and pyinotify 105 | * Update to Python 3 106 | 107 | ## 0.10.4-RC9 (2019/06/23) 108 | 109 | * Alpine Linux 3.10 110 | 111 | ## 0.10.4-RC8 (2019/05/06) 112 | 113 | * Add `kmod` (#23) 114 | 115 | ## 0.10.4-RC7 (2019/05/03) 116 | 117 | * Add `F2B_LOG_TARGET` var (#22) 118 | 119 | ## 0.10.4-RC6 (2019/04/24) 120 | 121 | * Add `ip6tables` 122 | 123 | ## 0.10.4-RC5 (2019/01/31) 124 | 125 | * Alpine Linux 3.9 126 | 127 | ## 0.10.4-RC4 (2018/11/18) 128 | 129 | * Add `F2B_IPTABLES_CHAIN` var to specify the iptables chain to which the Fail2Ban rules should be added 130 | * Change default action to `%(action_)s` 131 | * Add ipset 132 | 133 | ## 0.10.4-RC3 (2018/10/06) 134 | 135 | * Add `whois` (#6) 136 | 137 | ## 0.10.4-RC2 (2018/10/05) 138 | 139 | * Allow to add custom actions and filters through `/data/action.d` and `/data/filter.d` folders (#4) 140 | * Relocate database to `/data/db` and jails to `/data/jail.d` (breaking change, see README.md) 141 | 142 | ## 0.10.4-RC1 (2018/10/04) 143 | 144 | * Fail2ban 0.10.4 145 | 146 | ## 0.10.3.1-RC4 (2018/08/19) 147 | 148 | * Add `curl` (#1) 149 | 150 | ## 0.10.3.1-RC3 (2018/07/28) 151 | 152 | * Upgrade based image to Alpine Linux 3.8 153 | * Unset sensitive vars 154 | 155 | ## 0.10.3.1-RC2 (2018/05/07) 156 | 157 | * Add mail alerts configurations with SSMTP 158 | * Add healthcheck 159 | 160 | ## 0.10.3.1-RC1 (2018/04/25) 161 | 162 | * Initial version based on Fail2ban 0.10.3.1 163 | -------------------------------------------------------------------------------- /examples/jails/sshd/README.md: -------------------------------------------------------------------------------- 1 | ## SSHD 2 | 3 | To block IPs that have SSHD authentication failures on your host, you have to: 4 | 5 | * Copy files [jail.d](jail.d) to `./data` 6 | 7 | For example: 8 | 9 | ```console 10 | $ docker run -it --name fail2ban --restart always \ 11 | --network host \ 12 | --cap-add NET_ADMIN \ 13 | --cap-add NET_RAW \ 14 | -v $(pwd)/data:/data \ 15 | -v /var/log:/var/log:ro \ 16 | -e F2B_LOG_LEVEL=DEBUG \ 17 | crazymax/fail2ban:latest 18 | ``` 19 | 20 | Here is the log output if an IP is banned: 21 | 22 | ```text 23 | 2018-11-18 21:38:42,410 fail2ban.filterpoll [1]: DEBUG /var/log/auth.log has been modified 24 | 2018-11-18 21:38:44,427 fail2ban.filterpoll [1]: DEBUG /var/log/auth.log has been modified 25 | 2018-11-18 21:38:44,427 fail2ban.filter [1]: DEBUG Processing line with time:1542573523.0 and ip:192.168.51.100 26 | 2018-11-18 21:38:44,428 fail2ban.filter [1]: INFO [sshd] Found 192.168.51.100 - 2018-11-18 21:38:43 27 | 2018-11-18 21:38:44,428 fail2ban.failmanager [1]: DEBUG Total # of detected failures: 1. Current failures from 1 IPs (IP:count): 192.168.51.100:1 28 | 2018-11-18 21:38:52,580 fail2ban.filterpoll [1]: DEBUG /var/log/auth.log has been modified 29 | 2018-11-18 21:38:52,580 fail2ban.filter [1]: DEBUG Processing line with time:1542573532.0 and ip:192.168.51.100 30 | 2018-11-18 21:38:52,580 fail2ban.filter [1]: INFO [sshd] Found 192.168.51.100 - 2018-11-18 21:38:52 31 | 2018-11-18 21:38:52,581 fail2ban.failmanager [1]: DEBUG Total # of detected failures: 2. Current failures from 1 IPs (IP:count): 192.168.51.100:2 32 | 2018-11-18 21:38:55,196 fail2ban.filterpoll [1]: DEBUG /var/log/auth.log has been modified 33 | 2018-11-18 21:38:57,206 fail2ban.filterpoll [1]: DEBUG /var/log/auth.log has been modified 34 | 2018-11-18 21:38:57,413 fail2ban.filterpoll [1]: DEBUG /var/log/auth.log has been modified 35 | 2018-11-18 21:38:57,414 fail2ban.filter [1]: DEBUG Processing line with time:1542573537.0 and ip:192.168.51.100 36 | 2018-11-18 21:38:57,414 fail2ban.filter [1]: INFO [sshd] Found 192.168.51.100 - 2018-11-18 21:38:57 37 | 2018-11-18 21:38:57,414 fail2ban.failmanager [1]: DEBUG Total # of detected failures: 3. Current failures from 1 IPs (IP:count): 192.168.51.100:3 38 | 2018-11-18 21:38:58,626 fail2ban.filterpoll [1]: DEBUG /var/log/auth.log has been modified 39 | 2018-11-18 21:38:59,230 fail2ban.filterpoll [1]: DEBUG /var/log/auth.log has been modified 40 | 2018-11-18 21:38:59,230 fail2ban.filter [1]: DEBUG Processing line with time:1542573538.0 and ip:192.168.51.100 41 | 2018-11-18 21:38:59,230 fail2ban.filter [1]: INFO [sshd] Found 192.168.51.100 - 2018-11-18 21:38:58 42 | 2018-11-18 21:38:59,230 fail2ban.failmanager [1]: DEBUG Total # of detected failures: 4. Current failures from 1 IPs (IP:count): 192.168.51.100:4 43 | 2018-11-18 21:39:01,242 fail2ban.filterpoll [1]: DEBUG /var/log/auth.log has been modified 44 | 2018-11-18 21:39:01,242 fail2ban.filter [1]: DEBUG Processing line with time:1542573540.0 and ip:192.168.51.100 45 | 2018-11-18 21:39:01,243 fail2ban.filter [1]: INFO [sshd] Found 192.168.51.100 - 2018-11-18 21:39:00 46 | 2018-11-18 21:39:01,243 fail2ban.failmanager [1]: DEBUG Total # of detected failures: 5. Current failures from 1 IPs (IP:count): 192.168.51.100:5 47 | 2018-11-18 21:39:01,330 fail2ban.actions [1]: NOTICE [sshd] Ban 192.168.51.100 48 | 2018-11-18 21:39:01,331 fail2ban.action [1]: DEBUG iptables -w -N f2b-sshd 49 | iptables -w -A f2b-sshd -j RETURN 50 | iptables -w -I DOCKER-USER -p tcp -m multiport --dports ssh -j f2b-sshd 51 | 2018-11-18 21:39:01,357 fail2ban.utils [1]: DEBUG 7fdf90f4fd50 -- returned successfully 0 52 | 2018-11-18 21:39:01,358 fail2ban.action [1]: DEBUG iptables -w -n -L DOCKER-USER | grep -q 'f2b-sshd[ \t]' 53 | 2018-11-18 21:39:01,372 fail2ban.utils [1]: DEBUG 7fdf90ebdf30 -- returned successfully 0 54 | 2018-11-18 21:39:01,375 fail2ban.action [1]: DEBUG iptables -w -I f2b-sshd 1 -s 192.168.51.100 -j REJECT --reject-with icmp-port-unreachable 55 | 2018-11-18 21:39:01,394 fail2ban.utils [1]: DEBUG 7fdf90ecbe30 -- returned successfully 0 56 | 2018-11-18 21:39:01,395 fail2ban.actions [1]: DEBUG Banned 1 / 1, 1 ticket(s) in 'sshd' 57 | ``` 58 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | Latest Version 5 | Build Status 6 | Docker Stars 7 | Docker Pulls 8 |
Become a sponsor 9 | Donate Paypal 10 |

11 | 12 | ## About 13 | 14 | [Fail2ban](https://github.com/fail2ban/fail2ban) Docker image to ban hosts that cause 15 | multiple authentication errors. 16 | 17 | > [!TIP] 18 | > Want to be notified of new releases? Check out 🔔 [Diun (Docker Image Update Notifier)](https://github.com/crazy-max/diun) 19 | > project! 20 | 21 | ___ 22 | 23 | * [Build locally](#build-locally) 24 | * [Image](#image) 25 | * [Environment variables](#environment-variables) 26 | * [Volumes](#volumes) 27 | * [Usage](#usage) 28 | * [Docker Compose](#docker-compose) 29 | * [Command line](#command-line) 30 | * [Upgrade](#upgrade) 31 | * [Notes](#notes) 32 | * [`DOCKER-USER` chain](#docker-user-chain) 33 | * [`DOCKER-USER` and `INPUT` chains](#docker-user-and-input-chains) 34 | * [Jails examples](#jails-examples) 35 | * [Use fail2ban-client](#use-fail2ban-client) 36 | * [Global jail configuration](#global-jail-configuration) 37 | * [Custom jails, actions and filters](#custom-jails-actions-and-filters) 38 | * [Sending email using a sidecar container](#sending-email-using-a-sidecar-container) 39 | * [Contributing](#contributing) 40 | * [License](#license) 41 | 42 | ## Build locally 43 | 44 | ```shell 45 | git clone https://github.com/crazy-max/docker-fail2ban.git 46 | cd docker-fail2ban 47 | 48 | # Build image and output to docker (default) 49 | docker buildx bake 50 | 51 | # Build multi-platform image 52 | docker buildx bake image-all 53 | ``` 54 | 55 | ## Image 56 | 57 | | Registry | Image | 58 | |-----------------------------------------------------------------------------------------------------|------------------------------| 59 | | [Docker Hub](https://hub.docker.com/r/crazymax/fail2ban/) | `crazymax/fail2ban` | 60 | | [GitHub Container Registry](https://github.com/users/crazy-max/packages/container/package/fail2ban) | `ghcr.io/crazy-max/fail2ban` | 61 | 62 | Following platforms for this image are available: 63 | 64 | ``` 65 | $ docker buildx imagetools inspect crazymax/fail2ban --format "{{json .Manifest}}" | \ 66 | jq -r '.manifests[] | select(.platform.os != null and .platform.os != "unknown") | .platform | "\(.os)/\(.architecture)\(if .variant then "/" + .variant else "" end)"' 67 | 68 | linux/386 69 | linux/amd64 70 | linux/arm/v6 71 | linux/arm/v7 72 | linux/arm64 73 | linux/ppc64le 74 | linux/riscv64 75 | linux/s390x 76 | ``` 77 | 78 | ## Environment variables 79 | 80 | * `TZ`: The timezone assigned to the container (default `UTC`) 81 | * `F2B_LOG_TARGET`: Set the log target. This could be a file, SYSLOG, STDERR or STDOUT (default `STDOUT`) 82 | * `F2B_LOG_LEVEL`: Log level output (default `INFO`) 83 | * `F2B_DB_PURGE_AGE`: Age at which bans should be purged from the database (default `1d`) 84 | * `IPTABLES_MODE`: Choose between iptables `nft` or `legacy` mode. (default `auto`) 85 | 86 | ## Volumes 87 | 88 | * `/data`: Contains customs jails, actions and filters and Fail2ban persistent database 89 | 90 | ## Usage 91 | 92 | ### Docker Compose 93 | 94 | Docker compose is the recommended way to run this image. Copy the content of 95 | folder [examples/compose](examples/compose) in `/var/fail2ban/` on your host 96 | for example. Edit the Compose and env files with your preferences and run the 97 | following commands: 98 | 99 | ```console 100 | $ docker compose up -d 101 | $ docker compose logs -f 102 | ``` 103 | 104 | ### Command line 105 | 106 | You can also use the following minimal command : 107 | 108 | ```console 109 | $ docker run -d --name fail2ban --restart always \ 110 | --network host \ 111 | --cap-add NET_ADMIN \ 112 | --cap-add NET_RAW \ 113 | -v $(pwd)/data:/data \ 114 | -v /var/log:/var/log:ro \ 115 | crazymax/fail2ban:latest 116 | ``` 117 | 118 | ## Upgrade 119 | 120 | Recreate the container whenever I push an update: 121 | 122 | ```console 123 | $ docker compose pull 124 | $ docker compose up -d 125 | ``` 126 | 127 | ## Notes 128 | 129 | ### `DOCKER-USER` chain 130 | 131 | In Docker 17.06 and higher through [docker/libnetwork#1675](https://github.com/docker/libnetwork/pull/1675), 132 | you can add rules to a new table called `DOCKER-USER`, and these rules will be 133 | loaded before any rules Docker creates automatically. This is useful to make 134 | `iptables` rules created by Fail2Ban persistent. 135 | 136 | If you have an older version of Docker, you may just change the chain 137 | definition for your jail to `chain = FORWARD`. This way, all Fail2Ban rules 138 | come before any Docker rules but these rules will now apply to ALL forwarded 139 | traffic. 140 | 141 | More info : https://docs.docker.com/network/iptables/ 142 | 143 | ### `DOCKER-USER` and `INPUT` chains 144 | 145 | If your Fail2Ban container is attached to `DOCKER-USER` chain instead of 146 | `INPUT`, the rules will be applied **only to containers**. This means that any 147 | packets coming into the `INPUT` chain will bypass these rules that now reside 148 | under the `FORWARD` chain. 149 | 150 | This is why the [sshd](examples/jails/sshd) jail contains a [`chain = INPUT`](examples/jails/sshd/jail.d/sshd.conf) 151 | in its definition and [traefik](examples/jails/traefik) jail contains 152 | [`chain = DOCKER-USER`](examples/jails/traefik/jail.d/traefik.conf). 153 | 154 | ### Jails examples 155 | 156 | Here are some examples using the `DOCKER-USER` chain: 157 | 158 | * [guacamole](examples/jails/guacamole) 159 | * [traefik](examples/jails/traefik) 160 | 161 | And others using the `INPUT` chain: 162 | 163 | * [proxmox](examples/jails/proxmox) 164 | * [sshd](examples/jails/sshd) 165 | 166 | ### Use fail2ban-client 167 | 168 | [Fail2ban commands](http://www.fail2ban.org/wiki/index.php/Commands) can be used 169 | through the container. Here is an example if you want to ban an IP manually: 170 | 171 | ```console 172 | $ docker exec -t fail2ban-client set banip 173 | ``` 174 | 175 | ### Global jail configuration 176 | 177 | You can provide customizations in `/data/jail.d/*.local` files. 178 | 179 | For example, to change the default bantime for all jails: 180 | 181 | ```text 182 | [DEFAULT] 183 | bantime = 1h 184 | ``` 185 | 186 | > [!NOTE] 187 | > Loading order for jail configuration: 188 | > ```text 189 | > jail.conf 190 | > jail.d/*.conf (in alphabetical order) 191 | > jail.local 192 | > jail.d/*.local (in alphabetical order) 193 | > ``` 194 | 195 | A sample configuration file is [available on the official repository](https://github.com/fail2ban/fail2ban/blob/master/config/jail.conf). 196 | 197 | ### Custom jails, actions and filters 198 | 199 | Custom jails, actions and filters can be added respectively in `/data/jail.d`, 200 | `/data/action.d` and `/data/filter.d`. If you add an action/filter that already 201 | exists, it will be overriden. 202 | 203 | > [!WARNING] 204 | > Container has to be restarted to propagate changes 205 | 206 | ### Sending email using a sidecar container 207 | 208 | If you want to send emails using a sidecar container, see the example in 209 | [examples/smtp](examples/smtp). It uses the [smtp.py action](https://github.com/fail2ban/fail2ban/blob/1.1.0/config/action.d/smtp.py) 210 | and [msmtpd SMTP relay](https://github.com/crazy-max/docker-msmtpd) image. 211 | 212 | ## Contributing 213 | 214 | Want to contribute? Awesome! The most basic way to show your support is to star 215 | the project, or to raise issues. You can also support this project by [**becoming a sponsor on GitHub**](https://github.com/sponsors/crazy-max) 216 | or by making a [PayPal donation](https://www.paypal.me/crazyws) to ensure this 217 | journey continues indefinitely! 218 | 219 | Thanks again for your support, it is much appreciated! :pray: 220 | 221 | ## License 222 | 223 | MIT. See `LICENSE` for more details. 224 | --------------------------------------------------------------------------------