# fail2ban for Gitea, Forgejo and GitLab

> Stop Git server brute force with fail2ban: a Gitea/Forgejo filter, file logging, Docker (DOCKER-USER), built-in SSH, and the stock GitLab filter in 1.1.1.
>
> Source: https://fail2ban.dev/guides/gitea/ · Updated 2026-10-25 · Checked against fail2ban 1.1.1
> fail2ban.dev is an independent guide, not the official fail2ban project.

To protect Gitea or Forgejo with fail2ban, enable file logging in `app.ini` and create a filter that matches `Failed authentication attempt for <user> from <IP>`. Then add a jail that reads `gitea.log` and bans ports 80, 443 and your Git SSH port. Forgejo logs the same message. For GitLab, enable the stock `[gitlab]` jail, which reads `/var/log/gitlab/gitlab-rails/application.log`.

## What Gitea and Forgejo log

Gitea and Forgejo write a warning for every failed web login and every failed connection to their built-in SSH server. From the current source (`routers/web/auth/auth.go` and `modules/ssh/ssh.go`), with the default log format:

```log
2026/10/25 18:36:14 modules/ssh/ssh.go:249:sshConnectionFailed() [W] Failed authentication attempt from 198.51.100.7:40122
```

## Step 1: make Gitea log to a file

Gitea's default `[log]` mode is `console`, which goes to the journal or `docker logs`. Add a file:

```ini
[log]
MODE      = console, file
LEVEL     = Info
ROOT_PATH = /var/lib/gitea/log
```

If Gitea sits behind a reverse proxy, it must trust the proxy's `X-Forwarded-For` header. The defaults trust only loopback (`127.0.0.0/8,::1/128`). A proxy in another container or on another host needs its network added, or every failure is logged with the proxy's address:

```ini
[security]
REVERSE_PROXY_LIMIT           = 1
REVERSE_PROXY_TRUSTED_PROXIES = 127.0.0.0/8,::1/128,172.16.0.0/12
```

## Step 2: the filter

```ini
[Definition]
failregex = ^\s*\S+:\d+:\S+\(\) \[[WE]\] Failed authentication attempt (?:for <F-USER>.+?</F-USER> )?from <ADDR>(?::\d+)?(?::|\s*$)
ignoreregex =
```

```bash
```

## Step 3: the jail

```ini
[gitea]
enabled  = true
filter   = gitea
backend  = auto
logpath  = /var/lib/gitea/log/gitea.log
port     = http,https,2222
maxretry = 5
findtime = 1h
bantime  = 1h
```

```bash
sudo systemctl restart fail2ban
sudo fail2ban-client status gitea
```

Two things change when Gitea runs in a container.

**The log path.** In the official image, Gitea's data lives in `/data` and the log root is `/data/gitea/log`. Turn on file logging with an environment variable and read the file through the volume on the host:

```yaml
services:
  gitea:
    image: gitea/gitea:latest
    environment:
      - GITEA__log__MODE=console,file
      - GITEA__log__ROOT_PATH=/data/gitea/log
    volumes:
      - ./gitea:/data
    ports:
      - "3000:3000"
      - "2222:22"
```

**The ban action.** Traffic to a published Docker port is DNATed and goes through the `FORWARD` path, not `INPUT`. The default nftables action hooks `input`, so its bans never touch the container. Ban in Docker's `DOCKER-USER` chain instead:

```ini
[gitea]
enabled   = true
filter    = gitea
backend   = auto
logpath   = /srv/gitea/gitea/log/gitea.log
banaction = iptables-multiport
chain     = DOCKER-USER
port      = 3000,22
```

## SSH: built-in server vs OpenSSH

- **Gitea's built-in SSH server** (`START_SSH_SERVER = true`, or SSH inside the Docker image) logs failures to `gitea.log`, and the filter above catches them. Include its port in the jail.
- **OpenSSH passthrough** (the `git` user on the host's sshd): failures are logged by sshd, not Gitea. The stock [sshd jail](/guides/ssh/) already covers them.

Git clients with many keys loaded can produce warnings before the right key is accepted. The `sshConnectionFailed()` line is written only when all attempts failed, so it's safe to count.

## GitLab: the stock gitlab filter

fail2ban ships a `[gitlab]` jail and a [gitlab filter](/guides/gitea/#filter-gitlab) that match GitLab's `Failed Login` message in `application.log`:

```log
```
[gitlab]
enabled = true
backend = auto
port    = http,https
logpath = /var/log/gitlab/gitlab-rails/application.log
```

```ini
[Definition]
_groupre = (?:\s*"(?!(?:time|meta\.remote_ip|message)\b)\w[^"]+"\s*:\s*(?:"[^"]+"|\w+)\s*[,\}])
failregex = ^%(_groupre)s*"meta\.remote_ip":"<ADDR>"\s*,\s*%(_groupre)s*"message":"Failed Login: username=<F-USER>(?:\S*|[^"]*?)(?= ip=)</F-USER>
datepattern = ^\{%(_groupre)s*"time":"%%Y-%%m-%%dT%%H:%%M:%%S\.%%f%%z"\s*,\s*
ignoreregex =
```
[gitlab]
enabled = true
filter  = gitlab-json
backend = auto
port    = http,https
logpath = /var/log/gitlab/gitlab-rails/application_json.log
```

GitLab also has a built-in failed-authentication ban for Git over HTTP and rate limits in the admin area. fail2ban adds a firewall-level ban for the web sign-in page on top.

## The gitlab filter reference

{{component:filter-ref:gitlab}}

## FAQ

### Does Gitea have a fail2ban filter?

fail2ban doesn't ship one, but Gitea logs a consistent line: `Failed authentication attempt for <user> from <IP>:<port>`. Enable file logging (`[log] MODE = console, file`), create `/etc/fail2ban/filter.d/gitea.local` with a regex for that line, and add a jail that reads `gitea.log`.

### How do I use fail2ban with Gitea in Docker?

Set `GITEA__log__MODE=console,file` so the container writes `/data/gitea/log/gitea.log`, and read it through the volume on the host. Ban with `banaction = iptables-multiport` and `chain = DOCKER-USER` on the container ports (3000, 22), because published-port traffic bypasses the `INPUT` chain.

### Does the Gitea filter work for Forgejo?

Yes. Forgejo is a Gitea fork and logs the same `Failed authentication attempt for <user> from <IP>` message for web logins and `Failed authentication attempt from <IP>` for its SSH server. Use the same filter and point `logpath` at Forgejo's log directory, often `/var/lib/forgejo/log/`.

### Which GitLab log does the fail2ban gitlab filter read?

In fail2ban 1.1.1, `/var/log/gitlab/gitlab-rails/application.log`, matching `Failed Login: username=... ip=...`. GitLab deprecated that file in 15.10. For `application_json.log`, use a local JSON filter (shown above) until your fail2ban includes the `logtype = json` option.

### Why does fail2ban ban 127.0.0.1 or my proxy for Gitea?

Gitea is logging the reverse proxy's address instead of the client's. Add the proxy's network to `REVERSE_PROXY_TRUSTED_PROXIES` (default: loopback only) with `REVERSE_PROXY_LIMIT = 1` in `app.ini` so Gitea reads `X-Forwarded-For`, then check that new failure lines show real client IPs.
