diff --git a/landoapi/dockerflow.py b/landoapi/dockerflow.py
index 857191a8..26e1c9ba 100644
--- a/landoapi/dockerflow.py
+++ b/landoapi/dockerflow.py
@@ -7,9 +7,16 @@
 """
 
 import json
+import logging
 
+import requests
 from flask import Blueprint, current_app, jsonify
 
+from landoapi.phabricator_client import PhabricatorClient, \
+    PhabricatorAPIException
+
+logger = logging.getLogger(__name__)
+
 dockerflow = Blueprint('dockerflow', __name__)
 
 
@@ -21,8 +28,16 @@ def heartbeat():
     and return a 200 iff those services and the app itself are
     performing normally. Return a 5XX if something goes wrong.
     """
-    # TODO check backing services
-    return '', 200
+    phab = PhabricatorClient(api_key='')
+    try:
+        phab.check_connection()
+    except PhabricatorAPIException as exc:
+        logger.warning(
+            'heartbeat: problem, Phabricator API connection', exc_info=exc
+        )
+        return 'heartbeat: problem', 502
+    logger.info('heartbeat: ok, all services are up')
+    return 'heartbeat: ok', 200
 
 
 @dockerflow.route('/__lbheartbeat__')
diff --git a/landoapi/phabricator_client.py b/landoapi/phabricator_client.py
index 901a29f2..0e2d9b0a 100644
--- a/landoapi/phabricator_client.py
+++ b/landoapi/phabricator_client.py
@@ -2,11 +2,14 @@
 # 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/.
 
+import logging
 import os
 import requests
 
 from landoapi.utils import extract_rawdiff_id_from_uri
 
+logger = logging.getLogger(__name__)
+
 
 class PhabricatorClient:
     """ A class to interface with Phabricator's Conduit API.
@@ -145,6 +148,21 @@ def get_revision_repo(self, revision):
         """
         return self.get_repo(revision['repositoryPHID'])
 
+    def check_connection(self):
+        """Test the Phabricator API connection with conduit.ping.
+
+        Will return success iff the response has a HTTP status code of 200, the
+        JSON response is a well-formed Phabricator API response, and if there
+        is no connection error (like a hostname lookup error or timeout).
+
+        Raises a PhabricatorAPIException on error.
+        """
+        try:
+            self._GET('/conduit.ping')
+        except (requests.ConnectionError, requests.Timeout) as exc:
+            logging.debug("error calling 'conduit.ping': %s", exc)
+            raise PhabricatorAPIException from exc
+
     def _request(self, url, data=None, params=None, method='GET'):
         data = data if data else {}
         data['api.token'] = self.api_key
diff --git a/tests/conftest.py b/tests/conftest.py
index 2b3e41d8..6ab074ca 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -3,6 +3,7 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 import json
 
+import logging
 import pytest
 import requests_mock
 
@@ -57,7 +58,13 @@ def init_app(self, app):
 
 
 @pytest.fixture
-def app(versionfile, docker_env_vars, disable_migrations):
+def disable_log_output():
+    """Disable Python standard logging output to the console."""
+    logging.disable(logging.CRITICAL)
+
+
+@pytest.fixture
+def app(versionfile, docker_env_vars, disable_migrations, disable_log_output):
     """Needed for pytest-flask."""
     app = create_app(versionfile.strpath)
     return app.app
diff --git a/tests/test_dockerflow.py b/tests/test_dockerflow.py
index 39ba4f6f..40f69048 100644
--- a/tests/test_dockerflow.py
+++ b/tests/test_dockerflow.py
@@ -3,6 +3,12 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 import json
+import requests
+
+import requests_mock
+
+from tests.canned_responses.phabricator.revisions import CANNED_EMPTY_RESULT
+from tests.utils import phab_url
 
 
 def test_dockerflow_lb_endpoint_returns_200(client):
@@ -18,3 +24,30 @@ def test_dockerflow_version_endpoint_response(client):
 def test_dockerflow_version_matches_disk_contents(client, versionfile):
     response = client.get('/__version__')
     assert response.json == json.load(versionfile.open())
+
+
+def test_heartbeat_returns_200_if_phabricator_api_is_up(client):
+    json_response = CANNED_EMPTY_RESULT.copy()
+    with requests_mock.mock() as m:
+        m.get(phab_url('conduit.ping'), status_code=200, json=json_response)
+
+        response = client.get('/__heartbeat__')
+
+        assert m.called
+        assert response.status_code == 200
+
+
+def test_heartbeat_returns_http_502_if_phabricator_ping_returns_error(client):
+    error_json = {
+        "result": None,
+        "error_code": "ERR-CONDUIT-CORE",
+        "error_info": "BOOM"
+    }
+
+    with requests_mock.mock() as m:
+        m.get(phab_url('conduit.ping'), status_code=500, json=error_json)
+
+        response = client.get('/__heartbeat__')
+
+        assert m.called
+        assert response.status_code == 502
diff --git a/tests/test_phabricator_client.py b/tests/test_phabricator_client.py
index bed997fe..85cf3680 100644
--- a/tests/test_phabricator_client.py
+++ b/tests/test_phabricator_client.py
@@ -4,7 +4,9 @@
 """
 Tests for the PhabricatorClient
 """
+
 import pytest
+import requests
 import requests_mock
 
 from landoapi.phabricator_client import PhabricatorClient, \
@@ -122,6 +124,57 @@ def test_get_latest_patch_for_revision(phabfactory):
     assert returned_patch == patch
 
 
+def test_check_connection_success():
+    phab = PhabricatorClient(api_key='api-key')
+    success_json = CANNED_EMPTY_RESULT.copy()
+    with requests_mock.mock() as m:
+        m.get(phab_url('conduit.ping'), status_code=200, json=success_json)
+        phab.check_connection()
+        assert m.called
+
+
+def test_raise_exception_if_ping_encounters_connection_error():
+    phab = PhabricatorClient(api_key='api-key')
+    with requests_mock.mock() as m:
+        # Test with the generic ConnectionError, which is a superclass for
+        # other connection error types.
+        m.get(phab_url('conduit.ping'), exc=requests.ConnectionError)
+
+        with pytest.raises(PhabricatorAPIException):
+            phab.check_connection()
+        assert m.called
+
+
+def test_raise_exception_if_api_ping_times_out():
+    phab = PhabricatorClient(api_key='api-key')
+    with requests_mock.mock() as m:
+        # Test with the generic Timeout exception, which all other timeout
+        # exceptions derive from.
+        m.get(phab_url('conduit.ping'), exc=requests.Timeout)
+
+        with pytest.raises(PhabricatorAPIException):
+            phab.check_connection()
+        assert m.called
+
+
+def test_raise_exception_if_api_returns_error_json_response():
+    phab = PhabricatorClient(api_key='api-key')
+    error_json = {
+        "result": None,
+        "error_code": "ERR-CONDUIT-CORE",
+        "error_info": "BOOM"
+    }
+
+    with requests_mock.mock() as m:
+        # Test with the generic Timeout exception, which all other timeout
+        # exceptions derive from.
+        m.get(phab_url('conduit.ping'), status_code=500, json=error_json)
+
+        with pytest.raises(PhabricatorAPIException):
+            phab.check_connection()
+        assert m.called
+
+
 def test_phabricator_exception():
     """ Ensures that the PhabricatorClient converts JSON errors from Phabricator
     into proper exceptions with the error_code and error_message in tact.