diff -rupN a/ext/apache2/HelperAgent.cpp b/ext/apache2/HelperAgent.cpp
--- a/ext/apache2/HelperAgent.cpp	2017-11-24 13:06:06.011730096 +0100
+++ b/ext/apache2/HelperAgent.cpp	2017-11-24 14:47:50.618231332 +0100
@@ -24,6 +24,7 @@
  */
 
 #include <boost/shared_ptr.hpp>
+#include <boost/shared_array.hpp>
 #include <boost/bind.hpp>
 #include <oxt/thread.hpp>
 #include <oxt/system_calls.hpp>
@@ -200,6 +201,80 @@ private:
 				"': cannot set user ID", e);
 		}
 	}
+
+/**
+ * See warnIfPassengerRootVulnerable()
+ */
+static void
+warnIfPathVulnerable(const char *path, string &warnings) {
+	struct stat pathStat;
+
+	if (stat(path, &pathStat) == -1) {
+		P_DEBUG("Vulnerability check skipped: stat error on " << path << " (errno: " << errno << ")");
+		return; // fatal: we need that stat for both checks below
+	}
+
+	// Non-root ownership
+	struct passwd pathOwner;
+	struct passwd *pwdResult;
+
+	boost::shared_array<char> strings;
+	long stringsBufSize = std::max<long>(1024 * 128, sysconf(_SC_GETPW_R_SIZE_MAX));
+	strings.reset(new char[stringsBufSize]);
+	errno = 0;
+	if (getpwuid_r(pathStat.st_uid, &pathOwner, strings.get(), stringsBufSize, &pwdResult) == -1) {
+		P_DEBUG("Vulnerability check (owner) skipped: getpwuid_r error on " << path << " (owner UID: " <<
+				pathStat.st_uid << ", errno: " << errno << ")");
+	} else if (pwdResult == NULL) {
+		P_DEBUG("Vulnerability check (owner) skipped: getpwuid_r empty on " << path << " (owner UID: " <<
+				pathStat.st_uid << ", errno: " << errno << ")");
+	} else if (pathOwner.pw_uid != 0) {
+		warnings.append("\nThe path \"");
+		warnings.append(path);
+		warnings.append("\" can be modified by user \"");
+		warnings.append(pathOwner.pw_name);
+		warnings.append("\" (or applications running as that user). Change the owner of the path to root, or avoid running Passenger as root.");
+	}
+
+	// World writeable access rights
+	if ((pathStat.st_mode & S_IWOTH) != 0) {
+		warnings.append("\nThe path \"");
+		warnings.append(path);
+		warnings.append("\" is writeable by any user (or application). Limit write access on the path to only the root user/group.");
+	}
+}
+
+/*
+ * Emit a warning (log) if the Passenger root dir (and/or its parents) can be modified by non-root users
+ * while Passenger was run as root (because non-root users can then tamper with something running as root).
+ * It's just a convenience warning, so check failures are only logged at the debug level.
+ *
+ * N.B. we limit our checking to use cases that can easily (gotcha) lead to this vulnerable setup, such as
+ * installing Passenger via gem or tarball in a user dir, and then running it as root (for example by installing
+ * it as nginx or apache module). We do not check the entire installation file/dir structure for whether users have
+ * changed owner or access rights.
+ */
+static void
+warnIfPassengerRootVulnerable(const string &passengerRoot) {
+	TRACE_POINT();
+
+	if (geteuid() != 0) {
+		return; // Passenger is not root, so no escalation.
+	}
+
+	string checkPath = absolutizePath(passengerRoot);
+	// Check the Passenger root and all dirs above it for ownership and world-writeability
+	string warnings;
+	while (!checkPath.empty() && checkPath != "/") {
+		warnIfPathVulnerable(checkPath.c_str(), warnings);
+
+		checkPath = extractDirName(checkPath);
+	}
+	if (!warnings.empty()) {
+		P_WARN("WARNING: potential privilege escalation vulnerability. Passenger is running as root, and part(s) of the passenger root path (" <<
+				passengerRoot << ") can be changed by non-root user(s):" << warnings);
+	}
+}
 	
 public:
 	Server(FileDescriptor feedbackFd,
@@ -257,6 +332,7 @@ public:
 		messageServer->addHandler(ptr(new ExitHandler(exitEvent)));
 		
 		UPDATE_TRACE_POINT();
+                warnIfPassengerRootVulnerable(passengerRoot);
 		writeArrayMessage(feedbackFd,
 			"initialized",
 			"",  // Request socket filename; not available in the Apache helper server.
diff -rupN a/ext/common/Utils.cpp b/ext/common/Utils.cpp
--- a/ext/common/Utils.cpp	2017-11-24 13:06:05.994730224 +0100
+++ b/ext/common/Utils.cpp	2017-11-24 14:03:30.206636003 +0100
@@ -439,6 +439,67 @@ parseModeString(const StaticString &mode
 	return modeBits;
 }
 
+
+string
+absolutizePath(const StaticString &path, const StaticString &workingDir) {
+	vector<string> components;
+	if (!startsWith(path, "/")) {
+		if (workingDir.empty()) {
+			char buffer[PATH_MAX];
+			if (getcwd(buffer, sizeof(buffer)) == NULL) {
+				int e = errno;
+				throw SystemException("Unable to query current working directory", e);
+			}
+			split(buffer + 1, '/', components);
+		} else {
+			string absoluteWorkingDir = absolutizePath(workingDir);
+			split(StaticString(absoluteWorkingDir.data() + 1, absoluteWorkingDir.size() - 1),
+				'/', components);
+		}
+	}
+
+	const char *begin = path.data();
+	const char *end = path.data() + path.size();
+
+	// Skip leading slashes.
+	while (begin < end && *begin == '/') {
+		begin++;
+	}
+
+	while (begin < end) {
+		const char *next = (const char *) memchr(begin, '/', end - begin);
+		if (next == NULL) {
+			next = end;
+		}
+
+		StaticString component(begin, next - begin);
+		if (component == "..") {
+			if (!components.empty()) {
+				components.pop_back();
+			}
+		} else if (component != ".") {
+			components.push_back(component);
+		}
+
+		// Skip slashes until beginning of next path component.
+		begin = next + 1;
+		while (begin < end && *begin == '/') {
+			begin++;
+		}
+	}
+
+	string result;
+	vector<string>::const_iterator c_it, c_end = components.end();
+	for (c_it = components.begin(); c_it != c_end; c_it++) {
+		result.append("/");
+		result.append(*c_it);
+	}
+	if (result.empty()) {
+		result = "/";
+	}
+	return result;
+}
+
 const char *
 getSystemTempDir() {
 	const char *temp_dir = getenv("PASSENGER_TEMP_DIR");
diff -rupN a/ext/common/Utils.h b/ext/common/Utils.h
--- a/ext/common/Utils.h	2017-11-24 13:06:06.010730104 +0100
+++ b/ext/common/Utils.h	2017-11-24 14:04:43.414075942 +0100
@@ -217,6 +217,14 @@ string getProcessUsername();
 mode_t parseModeString(const StaticString &mode);
 
 /**
+ * Turns the given path into an absolute path. Unlike realpath(), this function does
+ * not resolve symlinks.
+ *
+ * @throws SystemException
+ */
+string absolutizePath(const StaticString &path, const StaticString &workingDir = StaticString());
+
+/**
  * Return the path name for the directory in which the system stores general
  * temporary files. This is usually "/tmp", but might be something else depending
  * on some environment variables.
diff -rupN a/ext/nginx/HelperAgent.cpp b/ext/nginx/HelperAgent.cpp
--- a/ext/nginx/HelperAgent.cpp	2017-11-24 13:06:06.015730066 +0100
+++ b/ext/nginx/HelperAgent.cpp	2017-11-24 14:47:06.080575437 +0100
@@ -41,6 +41,7 @@
 
 #include <boost/thread.hpp>
 #include <boost/shared_ptr.hpp>
+#include <boost/shared_array.hpp>
 #include "oxt/thread.hpp"
 #include "oxt/system_calls.hpp"
 
@@ -1158,6 +1159,80 @@ private:
 		}
 		return result;
 	}
+
+/**
+ * See warnIfPassengerRootVulnerable()
+ */
+static void
+warnIfPathVulnerable(const char *path, string &warnings) {
+	struct stat pathStat;
+
+	if (stat(path, &pathStat) == -1) {
+		P_DEBUG("Vulnerability check skipped: stat error on " << path << " (errno: " << errno << ")");
+		return; // fatal: we need that stat for both checks below
+	}
+
+	// Non-root ownership
+	struct passwd pathOwner;
+	struct passwd *pwdResult;
+
+	boost::shared_array<char> strings;
+	long stringsBufSize = std::max<long>(1024 * 128, sysconf(_SC_GETPW_R_SIZE_MAX));
+	strings.reset(new char[stringsBufSize]);
+	errno = 0;
+	if (getpwuid_r(pathStat.st_uid, &pathOwner, strings.get(), stringsBufSize, &pwdResult) == -1) {
+		P_DEBUG("Vulnerability check (owner) skipped: getpwuid_r error on " << path << " (owner UID: " <<
+				pathStat.st_uid << ", errno: " << errno << ")");
+	} else if (pwdResult == NULL) {
+		P_DEBUG("Vulnerability check (owner) skipped: getpwuid_r empty on " << path << " (owner UID: " <<
+				pathStat.st_uid << ", errno: " << errno << ")");
+	} else if (pathOwner.pw_uid != 0) {
+		warnings.append("\nThe path \"");
+		warnings.append(path);
+		warnings.append("\" can be modified by user \"");
+		warnings.append(pathOwner.pw_name);
+		warnings.append("\" (or applications running as that user). Change the owner of the path to root, or avoid running Passenger as root.");
+	}
+
+	// World writeable access rights
+	if ((pathStat.st_mode & S_IWOTH) != 0) {
+		warnings.append("\nThe path \"");
+		warnings.append(path);
+		warnings.append("\" is writeable by any user (or application). Limit write access on the path to only the root user/group.");
+	}
+}
+
+/*
+ * Emit a warning (log) if the Passenger root dir (and/or its parents) can be modified by non-root users
+ * while Passenger was run as root (because non-root users can then tamper with something running as root).
+ * It's just a convenience warning, so check failures are only logged at the debug level.
+ *
+ * N.B. we limit our checking to use cases that can easily (gotcha) lead to this vulnerable setup, such as
+ * installing Passenger via gem or tarball in a user dir, and then running it as root (for example by installing
+ * it as nginx or apache module). We do not check the entire installation file/dir structure for whether users have
+ * changed owner or access rights.
+ */
+static void
+warnIfPassengerRootVulnerable(const string &passengerRoot) {
+	TRACE_POINT();
+
+	if (geteuid() != 0) {
+		return; // Passenger is not root, so no escalation.
+	}
+
+	string checkPath = absolutizePath(passengerRoot);
+	// Check the Passenger root and all dirs above it for ownership and world-writeability
+	string warnings;
+	while (!checkPath.empty() && checkPath != "/") {
+		warnIfPathVulnerable(checkPath.c_str(), warnings);
+
+		checkPath = extractDirName(checkPath);
+	}
+	if (!warnings.empty()) {
+		P_WARN("WARNING: potential privilege escalation vulnerability. Passenger is running as root, and part(s) of the passenger root path (" <<
+				passengerRoot << ") can be changed by non-root user(s):" << warnings);
+	}
+}
 	
 public:
 	Server(FileDescriptor feedbackFd, pid_t webServerPid, const string &tempDir,
@@ -1220,6 +1295,7 @@ public:
 		messageServer->addHandler(ptr(new ExitHandler(exitEvent)));
 		
 		UPDATE_TRACE_POINT();
+                warnIfPassengerRootVulnerable(passengerRoot);
 		writeArrayMessage(feedbackFd,
 			"initialized",
 			getRequestSocketFilename().c_str(),
