Yes, given that condition, it’s appropriate to completely rely on Nginx + an external authentication service for authentication, and have the app only receive the authenticated username in the header.
Recommended Configuration
The primary candidate is Nginx + Authelia.
Internet
│
HTTPS :443
│
┌─────▼─────┐
│ nginx │
│ TLS termination │
│ auth_request
└─────┬─────┘
│
Authentication │ Unauthenticated
┌─────────────┘ │
▼ ▼
┌─────────────┐ ┌──────────────┐
│ Authelia │◀─────────▶│ auth.example │
│ password/MFA│ │ Login screen │
└─────────────┘ └──────────────┘
│
Authentication OK
│
▼
┌───────────────────┐
│ nginx │
│ Remote-User: foo │
│ is forcibly added │
└─────────┬─────────┘
│ private network only
▼
┌───────────────────┐
│ Existing App │
│ user = Remote-User│
└───────────────────┘
Authelia officially supports Nginx’s auth_request, and after successful authentication, it can return Remote-User, Remote-Groups, Remote-Email, etc. to Nginx. Nginx can receive the value with auth_request_set and pass it to the backend as a header. [Authelia][1]
Why Authelia?
It aligns well with the current requirements.
| Requirement | Nginx + Authelia |
|---|---|
| Minimize changes to existing app | ◎ |
| Username + password | ◎ |
Remote-User | ◎ Native |
| Continue using Nginx | ◎ |
| Local user DB | ◎ |
| LDAP integration | ◎ |
| Add MFA later | ◎ |
| Access control by group | ◎ |
| Brute-force protection | ◎ |
| Enable SSO | ◎ |
| Small-scale configuration | ◎ |
| Expand to multiple apps | ◎ |
Authelia’s file backend uses Argon2id and provides temporary bans for users/IPs based on the number of failed authentication attempts. [Authelia][2]
Changes on the App Side
In essence, the app needs to do this:
Remote-User: alice
Treat this as:
Currently logged-in user = alice
For example:
username = request.headers["Remote-User"]
if username is empty:
return 401
user = find_or_create_user(username)
However, there is a very important security boundary here.
The app itself must unconditionally trust that:
If
Remote-Useris present, the user is authenticated.
Therefore, the app must not be accessible by bypassing Nginx.
Most Important Point: Do Not Expose the App Directly
For example, in Docker:
app:
expose:
- "8080"
is okay, but:
ports:
- "8080:8080"
is not.
The configuration should only allow:
Internet ──► nginx ──► app
│
└──► Authelia
The following is NOT allowed:
Internet ──► nginx ──► app
│
└────────────────► app:8080 ← NG
If the latter is allowed, an attacker could bypass authentication by sending:
GET /
Remote-User: admin
directly.
Nginx Must Also Overwrite the Header
Do not forward the Remote-User sent from the browser as is.
The concept is:
location / {
auth_request /internal/authelia/authz;
auth_request_set $authenticated_user $upstream_http_remote_user;
proxy_set_header Remote-User $authenticated_user;
proxy_set_header X-Forwarded-User $authenticated_user;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
proxy_pass http://app:8080;
}
The key is:
proxy_set_header Remote-User $authenticated_user;
Even if the client sends:
Remote-User: admin
Nginx will replace it with the value obtained from the authentication service.
Authelia’s official Nginx configuration also uses:
auth_request_set $user $upstream_http_remote_user;
proxy_set_header Remote-User $user;
[Authelia][1]
Should I Use Remote-User or X-Forwarded-User?
In this case, Remote-User is recommended.
Authelia directly returns:
Remote-User
Remote-Groups
Remote-Name
Remote-Email
[Authelia][1]
For example:
Remote-User: tanaka
Remote-Email: tanaka@example.com
Remote-Groups: users,developers
The app generally only needs to look at:
Remote-User
If you need to add admin, editor, or viewer in the future, you can use Remote-Groups.
Authentication Flow
When a user accesses:
https://app.example.com/foo
the following happens:
1. Browser → nginx
2. nginx
└─ auth_request → Authelia
3. Authelia
├─ session exists → OK
└─ session does not exist → redirect to login
4. User
username/password entry
5. Authelia
└─ session cookie issued
6. Again, app.example.com/foo
7. nginx → Authelia
↓
alice
8. nginx → App
Remote-User: alice
Nginx’s auth_request allows access if the authentication subrequest returns a 2xx status code and denies access if it returns 401/403. [Nginx][3]
Authelia Side
If the number of users is small, you can store user information in a file.
The concept is:
authentication_backend:
file:
path: /config/users.yml
password:
algorithm: argon2
argon2:
variant: argon2id
The current Authelia file backend recommends Argon2id as the default setting. [Authelia][2]
For example, register users:
alice
bob
charlie
Instead of storing the passwords themselves, store:
$argon2id$...
the hash.
If Publishing to the Internet, Use default deny
It is a good idea for Authelia to have:
access_control:
default_policy: deny
rules:
- domain: app.example.com
policy: one_factor
Authelia itself recommends default_policy: deny. [Authelia][4]
Therefore, even if the configuration is wrong, it will not be:
No configuration → Public
but:
No configuration → Denied
You Can Start with Password-Only and Then Migrate to MFA
Initially, use:
policy: one_factor
and only:
username
password
Then, change to:
policy: two_factor
to:
password
+
TOTP / WebAuthn, etc.
Authelia has one_factor / two_factor as access control policies. [Authelia][4]
If you are placing the service on the public internet, it is worth considering configuring two-factor authentication (2FA) for administrators as well.
Brute-Force Protection
This is a major advantage compared to simple Nginx Basic Auth.
Authelia has, for example:
regulation:
modes:
- user
- ip
max_retries: 5
find_time: 2m
ban_time: 15m
as authentication attempt restrictions.
The current documentation also provides a mechanism to temporarily ban users/IPs based on the number of attempts to the username/password endpoint. [Authelia][5]
Storage Configuration
Small-Scale / Single Server
This is sufficient.
nginx
Authelia
Existing App
SQLite
users.yml
Authelia can use a local SQLite storage. The official example has this configuration. [Authelia][6]
It becomes quite easy with Docker Compose.
docker compose
├── nginx
├── authelia
└── app
- volumes:
authelia/config
authelia/db.sqlite3
If HA is Needed
At that point, you can migrate to:
nginx
│
┌──────┴──────┐
Authelia1 Authelia2
│ │
└──────┬───────┘
│
PostgreSQL
+
Redis
Authelia’s session storage supports memory for single-instance and Redis / Redis Sentinel for HA, and Redis-based is recommended for HA purposes. [Authelia][7]
There is no need to do this from the beginning.
TLS
This should be considered essential.
https://app.example.com
https://auth.example.com
For example:
Let's Encrypt
↓
nginx TLS termination
↓
internal docker network
Authelia itself requires HTTPS/WSS for both the authentication portal and forward authentication. [Authelia][8]
Nginx Basic Auth as a Simpler Alternative
In fact, if you want to add almost nothing, you can also use:
nginx
+
.htpasswd
location / {
auth_basic "Restricted";
auth_basic_user_file /etc/nginx/users.htpasswd;
proxy_set_header Remote-User $remote_user;
proxy_pass http://app:8080;
}
Nginx’s $remote_user contains the username from Basic Authentication. [Nginx][9]
Therefore, the extremely small configuration is also possible:
Browser
↓
Basic Auth
↓
nginx
↓
Remote-User: alice
↓
App
However
If it is for a general user service on the internet, I would treat this as a temporary configuration.
The reasons are:
- Cannot create a login screen
- Relies on the browser’s Basic Auth UX
- Explicit logout is difficult
- No password change UI
- Account management is
htpasswd - No MFA
- Weak lockout/authentication audit
- Difficult for future SSO
If it is a tool used by only 3 administrators, it is rational, but if there are general users, Authelia is easier to use.
Another Option: Authentik
Another viable option is:
nginx + authentik
Authentik also has a Forward Auth for Nginx, so you don’t need to proxy the existing app itself through authentik, and you can have:
nginx → Authentik asks for authentication
nginx → existing app
For authenticated users:
X-authentik-username
X-authentik-groups
X-authentik-email
X-authentik-name
X-authentik-uid
can be output. [authentik][11]
Furthermore, it is possible to convert to:
Remote-User
with a custom header. [authentik][12]
However, for the current use case, Authentik is slightly more complex than Authelia.
Summarizing the 3 Options
| Configuration | Ease of Use | Public-Facing | User Management | MFA | Suitable Cases |
|---|---|---|---|---|---|
| Nginx Basic Auth | ◎ | △ | △ | × | Few Users |
| Nginx + Authelia | ○ | ◎ | ○ | ◎ | This Case |
| Nginx + Authentik | △ | ◎ | ○ | ◎ | Full-fledged IAM/SSO |
Given the current requirements, Nginx + Authelia + Remote-User is the most balanced design.
If You Were to Actually Set It Up, This Configuration
Internet
│
TCP 80/443
│
┌───────▼───────┐
│ nginx │
│ │
│ TLS │
│ auth_request │
│ rate limit │
│ access log │
└───┬───────┬───┘
│ │
/auth │ │ /app
▼ │
┌─────────┐ │
│Authelia │ │
└────┬────┘ │
│ │
┌────▼────┐ │
│ users │ │
│ SQLite │ │
└─────────┘ │
│
Remote-User: alice
│
▼
┌──────────┐
│ App │
│ :8080 │
└──────────┘
nginx : public
Authelia : internal + only via nginx
App : internal only
SQLite/users.yml : filesystem only
Things the App Side Should Implement at a Minimum
Authentication itself does not need to be implemented, but the following should be included:
- Deny access if
Remote-Useris missing - Use
Remote-Useras the user ID - Do not allow connections from outside Nginx
- Switch users based on
Remote-Userand link all data access to that user - If username changes are allowed, consider designing with an immutable ID instead of the username.
In particular, 4 is important. Nginx/Authelia only solves the authentication of “this person is alice.”
alice can only read alice's data
bob can only read bob's data
admin can use management functions
The app itself must guarantee this authorization.
Conclusion
If the goal is a minimal configuration for publishing to the public, I recommend:
Let's Encrypt
↓
nginx
↓ auth_request
Authelia
↓
Remote-User
↓
Existing App
The changes on the app can be limited to “treating the trusted Remote-User as the current user,” and password storage, login screens, sessions, MFA, and brute-force protection can be completely separated from the app.
Next, it is possible to create a compose.yml, nginx.conf, Authelia configuration.yml, and users.yml that can be used to start nginx + Authelia + existing app with Docker Compose.