diff --git a/src/applications/config/option/PhabricatorDeveloperConfigOptions.php b/src/applications/config/option/PhabricatorDeveloperConfigOptions.php
--- a/src/applications/config/option/PhabricatorDeveloperConfigOptions.php
+++ b/src/applications/config/option/PhabricatorDeveloperConfigOptions.php
@@ -46,6 +46,31 @@
             "enable this option in production.\n\n".
             "You must enable DarkConsole by setting {{darkconsole.enabled}} ".
             "before this option will have any effect.")),
+      $this->newOption('debug.time-limit', 'int', null)
+        ->setSummary(
+          pht(
+            'Limit page execution time to debug hangs.'))
+        ->setDescription(
+          pht(
+            "This option can help debug pages which are taking a very ".
+            "long time (more than 30 seconds) to render.\n\n".
+            "If a page is slow to render (but taking less than 30 seconds), ".
+            "the best tools to use to figure out why it is slow are usually ".
+            "the DarkConsole service call profiler and XHProf.\n\n".
+            "However, if a request takes a very long time to return, some ".
+            "components (like Apache, nginx, or PHP itself) may abort the ".
+            "request before it finishes. This can prevent you from using ".
+            "profiling tools to understand page performance in detail.\n\n".
+            "In these cases, you can use this option to force the page to ".
+            "abort after a smaller number of seconds (for example, 10), and ".
+            "dump a useful stack trace. This can provide useful information ".
+            "about why a page is hanging.\n\n".
+            "To use this option, set it to a small number (like 10), and ".
+            "reload a hanging page. The page should exit after 10 seconds ".
+            "and give you a stack trace.\n\n".
+            "You should turn this option off (set it to 0) when you are ".
+            "done with it. Leaving it on creates a small amount of overhead ".
+            "for all requests, even if they do not hit the time limit.")),
       $this->newOption('debug.stop-on-redirect', 'bool', false)
         ->setBoolOptions(
           array(
diff --git a/support/PhabricatorStartup.php b/support/PhabricatorStartup.php
--- a/support/PhabricatorStartup.php
+++ b/support/PhabricatorStartup.php
@@ -38,6 +38,7 @@
 final class PhabricatorStartup {
 
   private static $startTime;
+  private static $debugTimeLimit;
   private static $globals = array();
   private static $capturingOutput;
   private static $rawInput;
@@ -226,6 +227,70 @@
   }
 
 
+/* -(  Debug Time Limit  )--------------------------------------------------- */
+
+
+  /**
+   * Set a time limit (in seconds) for the current script. After time expires,
+   * the script fatals.
+   *
+   * This works like `max_execution_time`, but prints out a useful stack trace
+   * when the time limit expires. This is primarily intended to make it easier
+   * to debug pages which hang by allowing extraction of a stack trace: set a
+   * short debug limit, then use the trace to figure out what's happening.
+   *
+   * The limit is implemented with a tick function, so enabling it implies
+   * some accounting overhead.
+   *
+   * @param int Time limit in seconds.
+   * @return void
+   */
+  public static function setDebugTimeLimit($limit) {
+    self::$debugTimeLimit = $limit;
+
+    static $initialized;
+    if (!$initialized) {
+      declare(ticks=1);
+      register_tick_function(array('PhabricatorStartup', 'onDebugTick'));
+    }
+  }
+
+
+  /**
+   * Callback tick function used by @{method:setDebugTimeLimit}.
+   *
+   * Fatals with a useful stack trace after the time limit expires.
+   *
+   * @return void
+   */
+  public static function onDebugTick() {
+    $limit = self::$debugTimeLimit;
+    if (!$limit) {
+      return;
+    }
+
+    $elapsed = (microtime(true) - self::getStartTime());
+    if ($elapsed > $limit) {
+      $frames = array();
+      foreach (debug_backtrace() as $frame) {
+        $file = isset($frame['file']) ? $frame['file'] : '-';
+        $file = basename($file);
+
+        $line = isset($frame['line']) ? $frame['line'] : '-';
+        $class = isset($frame['class']) ? $frame['class'].'->' : null;
+        $func = isset($frame['function']) ? $frame['function'].'()' : '?';
+
+        $frames[] = "{$file}:{$line} {$class}{$func}";
+      }
+
+      self::didFatal(
+        "Request aborted by debug time limit after {$limit} seconds.\n\n".
+        "STACK TRACE\n".
+        implode("\n", $frames));
+    }
+  }
+
+
 /* -(  In Case of Apocalypse  )---------------------------------------------- */
 
 
diff --git a/webroot/index.php b/webroot/index.php
--- a/webroot/index.php
+++ b/webroot/index.php
@@ -16,6 +16,12 @@
   PhabricatorStartup::loadCoreLibraries();
 
   PhabricatorEnv::initializeWebEnvironment();
+
+  $debug_time_limit = PhabricatorEnv::getEnvConfig('debug.time-limit');
+  if ($debug_time_limit) {
+    PhabricatorStartup::setDebugTimeLimit($debug_time_limit);
+  }
+
   $show_unexpected_traces = PhabricatorEnv::getEnvConfig(
     'phabricator.developer-mode');