diff --git a/pygitweb/auth.py b/pygitweb/auth.py
index 71b28fd..4ea4d4d 100644
--- a/pygitweb/auth.py
+++ b/pygitweb/auth.py
@@ -30,6 +30,7 @@ from pygitweb.auth_config import (
 )
 from pygitweb.auth_oauth import OAUTH_STATE_COOKIE_NAME, oauth_router
 from pygitweb.config import settings
+from pygitweb.gravatar import gravatar_url
 from pygitweb.permissions import Permission, PermissionPrincipal
 from pygitweb.sessions import create_session, get_session, revoke_session
 from pygitweb.templates_env import POSTAMBLE, PREAMBLE, env
@@ -138,7 +139,7 @@ def get_local_user(username: str) -> UserInDB | None:
 		if not entry.password:
 			continue
 		if hmac.compare_digest(entry.user, username):
-			return UserInDB(username=entry.user, password=entry.password)
+			return UserInDB(username=entry.user, password=entry.password, email=entry.email)
 	return None
 
 
@@ -150,7 +151,7 @@ def authenticate_local_user(username: str, password: str) -> User | None:
 		return None
 	if not hmac.compare_digest(user.password, password):
 		return None
-	return User(username=user.username)
+	return User(username=user.username, email=user.email)
 
 
 def decode_access_token(token: str) -> User | None:
@@ -261,7 +262,7 @@ async def login_form_submit(
 			oauth_provider=str(auth_config.oauth_provider).strip() or "OAuth",
 		)
 		return HTMLResponse(f"{pre}{body}{POSTAMBLE}")
-	sid = create_session(user.username, auth_method="local")
+	sid = create_session(user.username, email=user.email, auth_method="local")
 	resp = RedirectResponse(url=next_safe, status_code=303)
 	resp.set_cookie(
 		key=ACCESS_TOKEN_COOKIE_NAME,
@@ -309,7 +310,7 @@ async def login(form_data: Annotated[OAuth2PasswordRequestForm, Depends()]) -> d
 	user = authenticate_local_user(form_data.username, form_data.password)
 	if user is None:
 		raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Incorrect username or password")
-	sid = create_session(user.username, auth_method="local")
+	sid = create_session(user.username, email=user.email, auth_method="local")
 	return {"access_token": sid, "token_type": "bearer"}
 
 
@@ -323,13 +324,14 @@ async def auth_status(
 	token: Annotated[str | None, Depends(access_token_from_request)],
 ) -> dict[str, bool | str | None]:
 	if not settings.AUTH:
-		return {"auth_enabled": False, "username": None}
+		return {"auth_enabled": False, "username": None, "gravatar_url": None}
 	if not token:
-		return {"auth_enabled": True, "username": None}
-	u = decode_access_token(token)
-	if u is None:
-		return {"auth_enabled": True, "username": None}
-	return {"auth_enabled": True, "username": u.username}
+		return {"auth_enabled": True, "username": None, "gravatar_url": None}
+	rec = get_session(token)
+	if rec is None:
+		return {"auth_enabled": True, "username": None, "gravatar_url": None}
+	avatar = gravatar_url(rec.email) if rec.email else None
+	return {"auth_enabled": True, "username": rec.username, "gravatar_url": avatar}
 
 
 @auth_router.get(
diff --git a/pygitweb/auth.schema.json b/pygitweb/auth.schema.json
index ee968e1..d2b25cc 100644
--- a/pygitweb/auth.schema.json
+++ b/pygitweb/auth.schema.json
@@ -74,6 +74,11 @@
             "pass": {
               "type": "string",
               "minLength": 8
+            },
+            "email": {
+              "type": "string",
+              "format": "email",
+              "description": "Optional e-mail for Gravatar and permission grants by principal e-mail."
             }
           }
         }
diff --git a/pygitweb/auth_config.py b/pygitweb/auth_config.py
index b30d979..befb7eb 100644
--- a/pygitweb/auth_config.py
+++ b/pygitweb/auth_config.py
@@ -61,6 +61,17 @@ class LocalUser(BaseModel):
 
 	user: str = Field(min_length=2)
 	password: str = Field(default="", alias="pass", serialization_alias="pass")
+	email: str | None = None
+
+	@field_validator("email", mode="before")
+	@classmethod
+	def _normalize_email(cls, value: object) -> str | None:
+		if value is None or value == "":
+			return None
+		if isinstance(value, str):
+			stripped = value.strip()
+			return stripped or None
+		return str(value).strip() or None
 
 
 class AuthConfig(BaseModel):
diff --git a/pygitweb/auth_test.py b/pygitweb/auth_test.py
index 3eaae25..d1b0625 100644
--- a/pygitweb/auth_test.py
+++ b/pygitweb/auth_test.py
@@ -7,10 +7,12 @@ from unittest.mock import AsyncMock, patch
 import pytest
 from fastapi.testclient import TestClient
 
+from pygitweb.auth import ACCESS_TOKEN_COOKIE_NAME
 from pygitweb.auth_config import AuthConfig, LocalUser
 from pygitweb.auth_oauth import OAUTH_STATE_COOKIE_NAME, _seal_oauth_state
 from pygitweb.config import settings
 from pygitweb.conftest import clear_client_cookies
+from pygitweb.gravatar import gravatar_url
 from pygitweb.main import app
 from pygitweb.permissions import PERMISSION_ADD_PROJECTS, PermissionsMap
 from pygitweb.sessions import get_session
@@ -271,9 +273,47 @@ def test_oauth_callback_creates_session(client: TestClient, oauth_auth_config: A
 	assert rec.email == "user@example.com"
 	assert rec.auth_method == "oauth"
 	assert client.get("/users/me").json()["username"] == "user@example.com"
+	status = client.get("/auth/status").json()
+	assert status["username"] == "user@example.com"
+	assert status["gravatar_url"] == gravatar_url("user@example.com")
 	clear_client_cookies(client)
 
 
+def test_auth_status_no_gravatar_for_local_login(client: TestClient, local_auth_config: AuthConfig) -> None:
+	with patch.object(settings, "AUTH", True):
+		client.post(
+			"/login",
+			data={"username": "admin", "password": "secret", "next": "/"},
+			follow_redirects=False,
+		)
+		status = client.get("/auth/status").json()
+	assert status["username"] == "admin"
+	assert status["gravatar_url"] is None
+
+
+def test_local_user_email_gravatar(client: TestClient) -> None:
+	cfg = AuthConfig(
+		auth_mode="local",
+		local_users=[LocalUser(user="admin", password="secret", email="  admin@example.com  ")],
+	)
+	with (
+		patch.object(settings, "AUTH", True),
+		patch("pygitweb.auth_config.auth_config", cfg),
+		patch("pygitweb.auth.auth_config", cfg),
+		patch("pygitweb.main.auth_config", cfg),
+	):
+		client.post(
+			"/login",
+			data={"username": "admin", "password": "secret", "next": "/"},
+			follow_redirects=False,
+		)
+		status = client.get("/auth/status").json()
+	assert status["gravatar_url"] == gravatar_url("admin@example.com")
+	rec = get_session(client.cookies[ACCESS_TOKEN_COOKIE_NAME])
+	assert rec is not None
+	assert rec.email == "admin@example.com"
+
+
 def test_authenticate_local_user_disabled_in_oauth_only_mode() -> None:
 	from pygitweb.auth import authenticate_local_user
 
diff --git a/pygitweb/gravatar.py b/pygitweb/gravatar.py
new file mode 100644
index 0000000..efef065
--- /dev/null
+++ b/pygitweb/gravatar.py
@@ -0,0 +1,16 @@
+"""Gravatar avatar URLs (https://gravatar.com)."""
+
+from __future__ import annotations
+
+import hashlib
+from urllib.parse import urlencode
+
+_GRAVATAR_BASE = "https://www.gravatar.com/avatar"
+
+
+def gravatar_url(email: str, *, size: int = 64) -> str:
+	"""Return a Gravatar image URL for ``email`` (``d=404`` when no profile exists)."""
+	normalized = email.strip().lower().encode("utf-8")
+	digest = hashlib.md5(normalized).hexdigest()  # noqa: S324
+	params = urlencode({"s": str(size), "d": "404"})
+	return f"{_GRAVATAR_BASE}/{digest}?{params}"
diff --git a/pygitweb/gravatar_test.py b/pygitweb/gravatar_test.py
new file mode 100644
index 0000000..ef9f9f1
--- /dev/null
+++ b/pygitweb/gravatar_test.py
@@ -0,0 +1,10 @@
+from pygitweb.gravatar import gravatar_url
+
+
+def test_gravatar_url_known_hash() -> None:
+	url = gravatar_url("test@example.com", size=80)
+	assert url == "https://www.gravatar.com/avatar/55502f40dc8b7c769880b10874abc9d0?s=80&d=404"
+
+
+def test_gravatar_url_normalizes_email() -> None:
+	assert gravatar_url("  Test@Example.COM  ") == gravatar_url("test@example.com")
diff --git a/pygitweb/static/main.css b/pygitweb/static/main.css
index 13ded8f..0b5bb3f 100644
--- a/pygitweb/static/main.css
+++ b/pygitweb/static/main.css
@@ -573,6 +573,12 @@ tbody tr {
 		width: 1.25rem;
 		height: 1.25rem;
 	}
+
+	.avatar img {
+		width: 100%;
+		height: 100%;
+		object-fit: cover;
+	}
 }
 
 /* Diff/status helpers (use var(--pgw-add-*) etc. in markup if needed) */
diff --git a/pygitweb/static/navbar-auth.js b/pygitweb/static/navbar-auth.js
index 856947a..964c0a0 100644
--- a/pygitweb/static/navbar-auth.js
+++ b/pygitweb/static/navbar-auth.js
@@ -17,6 +17,30 @@
 		avatar.classList.add("bg-secondary");
 		avatar.innerHTML = userAvatarSvg();
 	}
+	function setFallbackAvatar(isAdmin) {
+		avatar.classList.remove("bg-secondary");
+		if (isAdmin) {
+			avatar.classList.remove("bg-teal");
+			avatar.classList.add("bg-primary", "text-white");
+			avatar.innerHTML = adminAvatarSvg();
+		} else {
+			avatar.classList.remove("bg-primary", "text-white");
+			avatar.classList.add("bg-teal");
+			avatar.innerHTML = userAvatarSvg();
+		}
+	}
+	function setGravatarAvatar(url, isAdmin) {
+		avatar.classList.remove("bg-secondary", "bg-primary", "bg-teal", "text-white");
+		var img = document.createElement("img");
+		img.src = url;
+		img.alt = "";
+		img.width = 32;
+		img.height = 32;
+		img.onerror = function () {
+			setFallbackAvatar(isAdmin);
+		};
+		avatar.replaceChildren(img);
+	}
 	fetch("/auth/status", { credentials: "same-origin" })
 		.then(function (r) {
 			return r.json();
@@ -37,15 +61,11 @@
 				link.href = "/user";
 				nameEl.textContent = s.username;
 				metaEl.textContent = "Signed in";
-				avatar.classList.remove("bg-secondary");
-				if (String(s.username).toLowerCase() === "admin") {
-					avatar.classList.remove("bg-teal");
-					avatar.classList.add("bg-primary", "text-white");
-					avatar.innerHTML = adminAvatarSvg();
+				var isAdmin = String(s.username).toLowerCase() === "admin";
+				if (s.gravatar_url) {
+					setGravatarAvatar(s.gravatar_url, isAdmin);
 				} else {
-					avatar.classList.remove("bg-primary", "text-white");
-					avatar.classList.add("bg-teal");
-					avatar.innerHTML = userAvatarSvg();
+					setFallbackAvatar(isAdmin);
 				}
 				return;
 			}
