diff --git a/src/applications/daemon/controller/PhabricatorDaemonLogViewController.php b/src/applications/daemon/controller/PhabricatorDaemonLogViewController.php
--- a/src/applications/daemon/controller/PhabricatorDaemonLogViewController.php
+++ b/src/applications/daemon/controller/PhabricatorDaemonLogViewController.php
@@ -54,6 +54,10 @@
         $tag->setName(pht('Waiting'));
         break;
       case PhabricatorDaemonLog::STATUS_EXITED:
+        $tag->setBackgroundColor(PHUITagView::COLOR_YELLOW);
+        $tag->setName(pht('Exiting'));
+        break;
+      case PhabricatorDaemonLog::STATUS_EXITED:
         $tag->setBackgroundColor(PHUITagView::COLOR_GREY);
         $tag->setName(pht('Exited'));
         break;
@@ -138,6 +142,10 @@
         break;
       case PhabricatorDaemonLog::STATUS_EXITED:
         $details = pht(
+          'This daemon is shutting down gracefully.');
+        break;
+      case PhabricatorDaemonLog::STATUS_EXITED:
+        $details = pht(
           'This daemon exited normally and is no longer running.');
         break;
     }
diff --git a/src/applications/daemon/event/PhabricatorDaemonEventListener.php b/src/applications/daemon/event/PhabricatorDaemonEventListener.php
--- a/src/applications/daemon/event/PhabricatorDaemonEventListener.php
+++ b/src/applications/daemon/event/PhabricatorDaemonEventListener.php
@@ -8,6 +8,7 @@
     $this->listen(PhutilDaemonOverseer::EVENT_DID_LAUNCH);
     $this->listen(PhutilDaemonOverseer::EVENT_DID_LOG);
     $this->listen(PhutilDaemonOverseer::EVENT_DID_HEARTBEAT);
+    $this->listen(PhutilDaemonOverseer::EVENT_WILL_GRACEFUL);
     $this->listen(PhutilDaemonOverseer::EVENT_WILL_EXIT);
   }
 
@@ -22,6 +23,9 @@
       case PhutilDaemonOverseer::EVENT_DID_LOG:
         $this->handleLogEvent($event);
         break;
+      case PhutilDaemonOverseer::EVENT_WILL_GRACEFUL:
+        $this->handleGracefulEvent($event);
+        break;
       case PhutilDaemonOverseer::EVENT_WILL_EXIT:
         $this->handleExitEvent($event);
         break;
@@ -86,6 +90,13 @@
     }
   }
 
+  private function handleGracefulEvent(PhutilEvent $event) {
+    $id = $event->getValue('id');
+
+    $daemon = $this->getDaemon($id);
+    $daemon->setStatus(PhabricatorDaemonLog::STATUS_EXITING)->save();
+  }
+
   private function handleExitEvent(PhutilEvent $event) {
     $id = $event->getValue('id');
 
diff --git a/src/applications/daemon/management/PhabricatorDaemonManagementRestartWorkflow.php b/src/applications/daemon/management/PhabricatorDaemonManagementRestartWorkflow.php
--- a/src/applications/daemon/management/PhabricatorDaemonManagementRestartWorkflow.php
+++ b/src/applications/daemon/management/PhabricatorDaemonManagementRestartWorkflow.php
@@ -9,11 +9,22 @@
       ->setSynopsis(
         pht(
           'Stop, then start the standard daemon loadout.'))
-      ->setArguments(array());
+      ->setArguments(
+        array(
+          array(
+            'name' => 'graceful',
+            'param' => 'seconds',
+            'help' => pht(
+              'Grace period for daemons to attempt a clean shutdown, in '.
+              'seconds. Defaults to __15__ seconds.'),
+            'default' => 15,
+          ),
+        ));
   }
 
   public function execute(PhutilArgumentParser $args) {
-    $err = $this->executeStopCommand(array());
+    $graceful = $args->getArg('graceful');
+    $err = $this->executeStopCommand(array(), $graceful);
     if ($err) {
       return $err;
     }
diff --git a/src/applications/daemon/management/PhabricatorDaemonManagementStopWorkflow.php b/src/applications/daemon/management/PhabricatorDaemonManagementStopWorkflow.php
--- a/src/applications/daemon/management/PhabricatorDaemonManagementStopWorkflow.php
+++ b/src/applications/daemon/management/PhabricatorDaemonManagementStopWorkflow.php
@@ -13,6 +13,14 @@
       ->setArguments(
         array(
           array(
+            'name' => 'graceful',
+            'param' => 'seconds',
+            'help' => pht(
+              'Grace period for daemons to attempt a clean shutdown, in '.
+              'seconds. Defaults to __15__ seconds.'),
+            'default' => 15,
+          ),
+          array(
             'name' => 'pids',
             'wildcard' => true,
           ),
@@ -21,7 +29,8 @@
 
   public function execute(PhutilArgumentParser $args) {
     $pids = $args->getArg('pids');
-    return $this->executeStopCommand($pids);
+    $graceful = $args->getArg('graceful');
+    return $this->executeStopCommand($pids, $graceful);
   }
 
 }
diff --git a/src/applications/daemon/management/PhabricatorDaemonManagementWorkflow.php b/src/applications/daemon/management/PhabricatorDaemonManagementWorkflow.php
--- a/src/applications/daemon/management/PhabricatorDaemonManagementWorkflow.php
+++ b/src/applications/daemon/management/PhabricatorDaemonManagementWorkflow.php
@@ -286,7 +286,7 @@
     return 0;
   }
 
-  protected final function executeStopCommand(array $pids) {
+  protected final function executeStopCommand(array $pids, $grace_period) {
     $console = PhutilConsole::getConsole();
 
     $daemons = $this->loadRunningDaemons();
@@ -325,39 +325,20 @@
     }
 
     $all_daemons = $running;
-    foreach ($running as $key => $daemon) {
-      $pid = $daemon->getPID();
-      $name = $daemon->getName();
 
-      $console->writeErr(pht("Stopping daemon '%s' (%s)...", $name, $pid)."\n");
-      if (!$daemon->isRunning()) {
-        $console->writeErr(pht('Daemon is not running.')."\n");
-        unset($running[$key]);
-        $daemon->updateStatus(PhabricatorDaemonLog::STATUS_EXITED);
-      } else {
-        posix_kill($pid, SIGINT);
-      }
+    // If we're doing a graceful shutdown, try SIGINT first.
+    if ($grace_period) {
+      $running = $this->sendSignal($running, SIGINT, $grace_period);
     }
 
-    $start = time();
-    do {
-      foreach ($running as $key => $daemon) {
-        $pid = $daemon->getPID();
-        if (!$daemon->isRunning()) {
-          $console->writeOut(pht('Daemon %s exited normally.', $pid)."\n");
-          unset($running[$key]);
-        }
-      }
-      if (empty($running)) {
-        break;
-      }
-      usleep(100000);
-    } while (time() < $start + 15);
+    // If we still have daemons, SIGTERM them.
+    if ($running) {
+      $running = $this->sendSignal($running, SIGTERM, 15);
+    }
 
-    foreach ($running as $key => $daemon) {
-      $pid = $daemon->getPID();
-      $console->writeErr(pht('Sending daemon %s a SIGKILL.', $pid)."\n");
-      posix_kill($pid, SIGKILL);
+    // If the overseer is still alive, SIGKILL it.
+    if ($running) {
+      $this->sendSignal($running, SIGKILL, 0);
     }
 
     foreach ($all_daemons as $daemon) {
@@ -369,6 +350,49 @@
     return 0;
   }
 
+  private function sendSignal(array $daemons, $signo, $wait) {
+    $console = PhutilConsole::getConsole();
+
+    foreach ($daemons as $key => $daemon) {
+      $pid = $daemon->getPID();
+      $name = $daemon->getName();
+
+      switch ($signo) {
+        case SIGINT:
+          $message = pht("Interrupting daemon '%s' (%s)...", $name, $pid);
+          break;
+        case SIGTERM:
+          $message = pht("Terminating daemon '%s' (%s)...", $name, $pid);
+          break;
+        case SIGKILL:
+          $message = pht("Killing daemon '%s' (%s)...", $name, $pid);
+          break;
+      }
+
+      $console->writeOut("%s\n", $message);
+      posix_kill($pid, $signo);
+    }
+
+    if ($wait) {
+      $start = PhabricatorTime::getNow();
+      do {
+        foreach ($daemons as $key => $daemon) {
+          $pid = $daemon->getPID();
+          if (!$daemon->isRunning()) {
+            $console->writeOut(pht('Daemon %s exited.', $pid)."\n");
+            unset($daemons[$key]);
+          }
+        }
+        if (empty($daemons)) {
+          break;
+        }
+        usleep(100000);
+      } while (PhabricatorTime::getNow() < $start + $wait);
+    }
+
+    return $daemons;
+  }
+
   private function freeActiveLeases() {
     $task_table = id(new PhabricatorWorkerActiveTask());
     $conn_w = $task_table->establishConnection('w');
diff --git a/src/applications/daemon/query/PhabricatorDaemonLogQuery.php b/src/applications/daemon/query/PhabricatorDaemonLogQuery.php
--- a/src/applications/daemon/query/PhabricatorDaemonLogQuery.php
+++ b/src/applications/daemon/query/PhabricatorDaemonLogQuery.php
@@ -67,6 +67,7 @@
     $status_running = PhabricatorDaemonLog::STATUS_RUNNING;
     $status_unknown = PhabricatorDaemonLog::STATUS_UNKNOWN;
     $status_wait = PhabricatorDaemonLog::STATUS_WAIT;
+    $status_exiting = PhabricatorDaemonLog::STATUS_EXITING;
     $status_exited = PhabricatorDaemonLog::STATUS_EXITED;
     $status_dead = PhabricatorDaemonLog::STATUS_DEAD;
 
@@ -77,7 +78,8 @@
       $seen = $daemon->getDateModified();
 
       $is_running = ($status == $status_running) ||
-                    ($status == $status_wait);
+                    ($status == $status_wait) ||
+                    ($status == $status_exiting);
 
       // If we haven't seen the daemon recently, downgrade its status to
       // unknown.
@@ -160,6 +162,7 @@
           PhabricatorDaemonLog::STATUS_UNKNOWN,
           PhabricatorDaemonLog::STATUS_RUNNING,
           PhabricatorDaemonLog::STATUS_WAIT,
+          PhabricatorDaemonLog::STATUS_EXITING,
         );
       default:
         throw new Exception("Unknown status '{$status}'!");
diff --git a/src/applications/daemon/storage/PhabricatorDaemonLog.php b/src/applications/daemon/storage/PhabricatorDaemonLog.php
--- a/src/applications/daemon/storage/PhabricatorDaemonLog.php
+++ b/src/applications/daemon/storage/PhabricatorDaemonLog.php
@@ -7,6 +7,7 @@
   const STATUS_RUNNING = 'run';
   const STATUS_DEAD    = 'dead';
   const STATUS_WAIT    = 'wait';
+  const STATUS_EXITING  = 'exiting';
   const STATUS_EXITED  = 'exit';
 
   protected $daemon;
diff --git a/src/applications/daemon/view/PhabricatorDaemonLogListView.php b/src/applications/daemon/view/PhabricatorDaemonLogListView.php
--- a/src/applications/daemon/view/PhabricatorDaemonLogListView.php
+++ b/src/applications/daemon/view/PhabricatorDaemonLogListView.php
@@ -17,8 +17,7 @@
       throw new Exception('Call setUser() before rendering!');
     }
 
-    $list = id(new PHUIObjectItemListView())
-      ->setFlush(true);
+    $list = new PHUIObjectItemListView();
     foreach ($this->daemonLogs as $log) {
       $id = $log->getID();
       $epoch = $log->getDateCreated();
@@ -43,6 +42,10 @@
               'dead.'));
           $item->addIcon('fa-times grey', pht('Dead'));
           break;
+        case PhabricatorDaemonLog::STATUS_EXITING:
+          $item->addAttribute(pht('This daemon is exiting.'));
+          $item->addIcon('fa-check', pht('Exiting'));
+          break;
         case PhabricatorDaemonLog::STATUS_EXITED:
           $item->setDisabled(true);
           $item->addAttribute(pht('This daemon exited cleanly.'));
diff --git a/src/applications/fact/daemon/PhabricatorFactDaemon.php b/src/applications/fact/daemon/PhabricatorFactDaemon.php
--- a/src/applications/fact/daemon/PhabricatorFactDaemon.php
+++ b/src/applications/fact/daemon/PhabricatorFactDaemon.php
@@ -8,7 +8,7 @@
 
   public function run() {
     $this->setEngines(PhabricatorFactEngine::loadAllEngines());
-    while (true) {
+    while (!$this->shouldExit()) {
       $iterators = $this->getAllApplicationIterators();
       foreach ($iterators as $iterator_name => $iterator) {
         $this->processIteratorWithCursor($iterator_name, $iterator);
diff --git a/src/applications/repository/daemon/PhabricatorRepositoryPullLocalDaemon.php b/src/applications/repository/daemon/PhabricatorRepositoryPullLocalDaemon.php
--- a/src/applications/repository/daemon/PhabricatorRepositoryPullLocalDaemon.php
+++ b/src/applications/repository/daemon/PhabricatorRepositoryPullLocalDaemon.php
@@ -71,7 +71,7 @@
     $futures = array();
     $queue = array();
 
-    while (true) {
+    while (!$this->shouldExit()) {
       $pullable = $this->loadPullableRepositories($include, $exclude);
 
       // If any repositories have the NEEDS_UPDATE flag set, pull them
@@ -422,6 +422,12 @@
           new PhutilNumber($sleep_duration)));
 
       $this->sleep(1);
+
+      if ($this->shouldExit()) {
+        $this->log(pht('Awakened from sleep by graceful shutdown!'));
+        return;
+      }
+
       if ($this->loadRepositoryUpdateMessages()) {
         $this->log(pht('Awakened from sleep by pending updates!'));
         break;
diff --git a/src/infrastructure/daemon/bot/PhabricatorBot.php b/src/infrastructure/daemon/bot/PhabricatorBot.php
--- a/src/infrastructure/daemon/bot/PhabricatorBot.php
+++ b/src/infrastructure/daemon/bot/PhabricatorBot.php
@@ -103,7 +103,7 @@
       foreach ($this->handlers as $handler) {
         $handler->runBackgroundTasks();
       }
-    } while (true);
+    } while (!$this->shouldExit());
   }
 
   public function writeMessage(PhabricatorBotMessage $message) {
diff --git a/src/infrastructure/daemon/garbagecollector/PhabricatorGarbageCollectorDaemon.php b/src/infrastructure/daemon/garbagecollector/PhabricatorGarbageCollectorDaemon.php
--- a/src/infrastructure/daemon/garbagecollector/PhabricatorGarbageCollectorDaemon.php
+++ b/src/infrastructure/daemon/garbagecollector/PhabricatorGarbageCollectorDaemon.php
@@ -31,7 +31,7 @@
       // scans just to delete a handful of rows; wake up in a few hours.
       $this->log(pht('All caught up, waiting for more garbage.'));
       $this->sleep(4 * (60 * 60));
-    } while (true);
+    } while (!$this->shouldExit());
 
   }
 
diff --git a/src/infrastructure/daemon/workers/PhabricatorTaskmasterDaemon.php b/src/infrastructure/daemon/workers/PhabricatorTaskmasterDaemon.php
--- a/src/infrastructure/daemon/workers/PhabricatorTaskmasterDaemon.php
+++ b/src/infrastructure/daemon/workers/PhabricatorTaskmasterDaemon.php
@@ -40,7 +40,7 @@
       }
 
       $this->sleep($sleep);
-    } while (true);
+    } while (!$this->shouldExit());
   }
 
 }