diff --git a/.drone.yml b/.drone.yml
index b7609a5382..a24f4c621e 100644
--- a/.drone.yml
+++ b/.drone.yml
@@ -207,8 +207,14 @@ steps:
     commands:
       - git update-ref refs/heads/tag_test ${DRONE_COMMIT_SHA}
 
+  - name: fix-permissions
+    image: gitea/test_env:linux-amd64  # https://gitea.com/gitea/test-env
+    commands:
+      - chown -R gitea:gitea .
+
   - name: unit-test
-    image: golang:1.17
+    image: gitea/test_env:linux-amd64  # https://gitea.com/gitea/test-env
+    user: gitea
     commands:
       - make unit-test-coverage test-check
     environment:
@@ -220,7 +226,8 @@ steps:
 
   - name: unit-test-gogit
     pull: always
-    image: golang:1.17
+    image: gitea/test_env:linux-amd64  # https://gitea.com/gitea/test-env
+    user: gitea
     commands:
       - make unit-test-coverage test-check
     environment:
@@ -232,6 +239,7 @@ steps:
 
   - name: test-mysql
     image: gitea/test_env:linux-amd64  # https://gitea.com/gitea/test-env
+    user: gitea
     commands:
       - make test-mysql-migration integration-test-coverage
     environment:
@@ -246,6 +254,7 @@ steps:
 
   - name: test-mysql8
     image: gitea/test_env:linux-amd64  # https://gitea.com/gitea/test-env
+    user: gitea
     commands:
       - timeout -s ABRT 40m make test-mysql8-migration test-mysql8
     environment:
@@ -259,6 +268,7 @@ steps:
 
   - name: test-mssql
     image: gitea/test_env:linux-amd64  # https://gitea.com/gitea/test-env
+    user: gitea
     commands:
       - make test-mssql-migration test-mssql
     environment:
@@ -343,9 +353,15 @@ steps:
         exclude:
           - pull_request
 
+  - name: fix-permissions
+    image: gitea/test_env:linux-arm64  # https://gitea.com/gitea/test-env
+    commands:
+      - chown -R gitea:gitea .
+
   - name: build
     pull: always
-    image: golang:1.17
+    image: gitea/test_env:linux-arm64  # https://gitea.com/gitea/test-env
+    user: gitea
     commands:
       - make backend
     environment:
@@ -355,6 +371,7 @@ steps:
 
   - name: test-sqlite
     image: gitea/test_env:linux-arm64  # https://gitea.com/gitea/test-env
+    user: gitea
     commands:
       - timeout -s ABRT 40m make test-sqlite-migration test-sqlite
     environment:
@@ -368,6 +385,7 @@ steps:
 
   - name: test-pgsql
     image: gitea/test_env:linux-arm64  # https://gitea.com/gitea/test-env
+    user: gitea
     commands:
       - timeout -s ABRT 40m make test-pgsql-migration test-pgsql
     environment:
diff --git a/modules/setting/setting.go b/modules/setting/setting.go
index 88302be1d3..2133184cfc 100644
--- a/modules/setting/setting.go
+++ b/modules/setting/setting.go
@@ -902,6 +902,9 @@ func NewContext() {
 	}
 
 	RunUser = Cfg.Section("").Key("RUN_USER").MustString(user.CurrentUsername())
+	// The following is a purposefully undocumented option. Please do not run Gitea as root. It will only cause future headaches.
+	// Please don't use root as a bandaid to "fix" something that is broken, instead the broken thing should instead be fixed properly.
+	unsafeAllowRunAsRoot := Cfg.Section("").Key("I_AM_BEING_UNSAFE_RUNNING_AS_ROOT").MustBool(false)
 	RunMode = Cfg.Section("").Key("RUN_MODE").MustString("prod")
 	// Does not check run user when the install lock is off.
 	if InstallLock {
@@ -911,6 +914,15 @@ func NewContext() {
 		}
 	}
 
+	// check if we run as root
+	if os.Getuid() == 0 {
+		if !unsafeAllowRunAsRoot {
+			// Special thanks to VLC which inspired the wording of this messaging.
+			log.Fatal("Gitea is not supposed to be run as root. Sorry. If you need to use privileged TCP ports please instead use setcap and the `cap_net_bind_service` permission")
+		}
+		log.Critical("You are running Gitea using the root user, and have purposely chosen to skip built-in protections around this. You have been warned against this.")
+	}
+
 	SSH.BuiltinServerUser = Cfg.Section("server").Key("BUILTIN_SSH_SERVER_USER").MustString(RunUser)
 
 	newRepository()