diff --git a/lib/db/mysql_wrapper.js b/lib/db/mysql_wrapper.js
index 91c4634827d590ed31bc007ad03decbd4fcbec8b..45edfd7de9411ad1fbb41618544d635cb62ee1f1 100644
--- a/lib/db/mysql_wrapper.js
+++ b/lib/db/mysql_wrapper.js
@@ -40,7 +40,13 @@ exports.createClient = function(options) {
       });
     },
     ping: function(cb) {
-      this.realClient.ping(cb);
+      if (stalled) {
+        process.nextTick(function() {
+          cb("database is intentionally stalled");
+        });
+      } else {
+        this.realClient.ping(cb);
+      }
     },
     _runNextQuery: function() {
       var self = this;
diff --git a/lib/wsapi.js b/lib/wsapi.js
index e23a50ce9872722011262ece41da40e4e4cccbf0..7ee1f4a8d24e85b058a316fb657d2e3585831c6a 100644
--- a/lib/wsapi.js
+++ b/lib/wsapi.js
@@ -251,8 +251,10 @@ exports.setup = function(options, app) {
     try {
       var api = require(path.join(__dirname, 'wsapi', f));
 
-      // don't register read apis if we are configured as a writer
-      if (options.only_write_apis && !api.writes_db) return;
+      // don't register read apis if we are configured as a writer,
+      // with the exception of ping which tests database connection health.
+      if (options.only_write_apis && !api.writes_db &&
+          operation != 'ping') return;
 
       wsapis[operation] = api;
 
diff --git a/lib/wsapi/ping.js b/lib/wsapi/ping.js
new file mode 100644
index 0000000000000000000000000000000000000000..e1847ef09dc3a6b548364186b08d10b850642621
--- /dev/null
+++ b/lib/wsapi/ping.js
@@ -0,0 +1,16 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const db = require('../db.js');
+
+exports.method = 'get';
+exports.writes_db = false;
+exports.i18n = false;
+
+exports.process = function(req, res) {
+  db.ping(function(err) {
+    if (err) res.send("fail", 500);
+    else res.send("ok",200);
+  });
+};
diff --git a/tests/stalled-mysql-test.js b/tests/stalled-mysql-test.js
index a0753552763a36b9a1f434c1986e47b64cee4ca8..cd40adba4c379ee97fa585626ba816ff46eb6cd7 100755
--- a/tests/stalled-mysql-test.js
+++ b/tests/stalled-mysql-test.js
@@ -77,6 +77,13 @@ suite.addBatch({
 
 // now try all apis that can be excercised without further setup
 suite.addBatch({
+  "ping": {
+    topic: wsapi.get('/wsapi/ping', {}),
+    "fails with 500 when db is stalled": function(err, r) {
+      // address info with a primary address doesn't need db access.
+      assert.strictEqual(r.code, 500);
+    }
+  },
   "address_info": {
     topic: wsapi.get('/wsapi/address_info', {
       email: 'test@example.domain'
@@ -154,6 +161,16 @@ addStallDriverBatch(false);
 
 var token = undefined;
 
+suite.addBatch({
+  "ping": {
+    topic: wsapi.get('/wsapi/ping', {}),
+    "works when database is unstalled": function(err, r) {
+      // address info with a primary address doesn't need db access.
+      assert.strictEqual(r.code, 200);
+    }
+  }
+});
+
 suite.addBatch({
   "account staging": {
     topic: wsapi.post('/wsapi/stage_user', {
@@ -194,6 +211,13 @@ addStallDriverBatch(true);
 // test remaining wsapis
 
 suite.addBatch({
+  "ping": {
+    topic: wsapi.get('/wsapi/ping', { }),
+    "fails": function(err, r) {
+      assert.strictEqual(r.code, 500);
+    }
+  },
+
   "account_cancel": {
     topic: wsapi.post('/wsapi/account_cancel', { }),
     "fails with 503": function(err, r) {