diff --git a/marathon_acme/clients.py b/marathon_acme/clients.py
index 8427381..4987d31 100644
--- a/marathon_acme/clients.py
+++ b/marathon_acme/clients.py
@@ -3,7 +3,7 @@ import json
 
 from requests.exceptions import HTTPError
 from treq.client import HTTPClient as treq_HTTPClient
-from twisted.internet.defer import gatherResults
+from twisted.internet.defer import DeferredList
 from twisted.logger import Logger, LogLevel
 from twisted.web.http import OK
 from uritools import uricompose, uridecode, urisplit
@@ -331,18 +331,63 @@ class MarathonLbClient(HTTPClient):
     def __init__(self, endpoints, *args, **kwargs):
         """
         :param endpoints:
-        The list of marathon-lb endpoints. All marathon-lb endpoints will be
-        called at once for any request.
+            The list of marathon-lb endpoints. All marathon-lb endpoints will
+            be called at once for any request.
         """
         super(MarathonLbClient, self).__init__(*args, **kwargs)
         self.endpoints = endpoints
 
     def request(self, *args, **kwargs):
-        requests = []
-        for endpoint in self.endpoints:
-            requests.append(super(MarathonLbClient, self).request(
-                *args, url=endpoint, **kwargs))
-        return gatherResults(requests)
+        return (
+            DeferredList(
+                [self._request(e, *args, **kwargs) for e in self.endpoints],
+                consumeErrors=True)
+            .addCallback(self._check_request_results))
+
+    def _request(self, endpoint, *args, **kwargs):
+        """
+        Perform a request to a specific endpoint. Raise an error if the status
+        code indicates a client or server error.
+        """
+        kwargs['url'] = endpoint
+        return (super(MarathonLbClient, self).request(*args, **kwargs)
+                .addCallback(raise_for_status))
+
+    def _check_request_results(self, results):
+        """
+        Check the result of each request that we made. If a failure occurred,
+        but some requests succeeded, log and count the failures. If all
+        requests failed, raise an error.
+
+        :return:
+            The list of responses, with a None value for any requests that
+            failed.
+        """
+        responses = []
+        failed_endpoints = []
+        for index, result_tuple in enumerate(results):
+            success, result = result_tuple
+            if success:
+                responses.append(result)
+            else:
+                endpoint = self.endpoints[index]
+                self.log.failure(
+                    'Failed to make a request to a marathon-lb instance: '
+                    '{endpoint}', result, LogLevel.error, endpoint=endpoint)
+                responses.append(None)
+                failed_endpoints.append(endpoint)
+
+        if len(failed_endpoints) == len(self.endpoints):
+            raise RuntimeError(
+                'Failed to make a request to all marathon-lb instances')
+
+        if failed_endpoints:
+            self.log.error(
+                'Failed to make a request to {x}/{y} marathon-lb instances: '
+                '{endpoints}', x=len(failed_endpoints), y=len(self.endpoints),
+                endpoints=failed_endpoints)
+
+        return responses
 
     def mlb_signal_hup(self):
         """

Only fail when all load balancers can't be reached
We should allow for one/some of the load balancers to be down and carry on when others are happy.
diff --git a/marathon_acme/tests/test_clients.py b/marathon_acme/tests/test_clients.py
index 9e95f45..6eb67f9 100644
--- a/marathon_acme/tests/test_clients.py
+++ b/marathon_acme/tests/test_clients.py
@@ -2,7 +2,8 @@ import json
 
 from testtools import ExpectedException, TestCase
 from testtools.assertions import assert_that
-from testtools.matchers import Equals, Is, IsInstance, MatchesStructure
+from testtools.matchers import (
+    Equals, Is, IsInstance, HasLength, MatchesStructure)
 from testtools.twistedsupport import (
     AsynchronousDeferredRunTest, failed, flush_logged_errors)
 from treq.client import HTTPClient as treq_HTTPClient
@@ -824,6 +825,97 @@ class TestMarathonLbClient(TestHTTPClientBase):
         return MarathonLbClient(['http://lb1:9090', 'http://lb2:9090'],
                                 client=client)
 
+    @inlineCallbacks
+    def test_request_success(self):
+        """
+        When a request is made, it is made to all marathon-lb instances and
+        the responses are returned.
+        """
+        d = self.cleanup_d(self.client.request('GET', path='/my-path'))
+
+        for lb in ['lb1', 'lb2']:
+            request = yield self.requests.get()
+            self.assertThat(request, HasRequestProperties(
+                method='GET', url='http://%s:9090/my-path' % (lb,)))
+
+            request.setResponseCode(200)
+            request.finish()
+
+        responses = yield d
+        self.assertThat(responses, HasLength(2))
+        for response in responses:
+            self.assertThat(response.code, Equals(200))
+
+    @inlineCallbacks
+    def test_request_partial_failure(self):
+        """
+        When a request is made and an error status code is returned from some
+        (but not all) of the matathon-lb instances, then the request returns
+        the list of responses with a None value for the unhappy request.
+        """
+        d = self.cleanup_d(self.client.request('GET', path='/my-path'))
+
+        lb1_request = yield self.requests.get()
+        self.assertThat(lb1_request, HasRequestProperties(
+            method='GET', url='http://lb1:9090/my-path'))
+
+        lb2_request = yield self.requests.get()
+        self.assertThat(lb2_request, HasRequestProperties(
+            method='GET', url='http://lb2:9090/my-path'))
+
+        # Fail the first one
+        lb1_request.setResponseCode(500)
+        lb1_request.setHeader('content-type', 'text/plain')
+        lb1_request.write(b'Internal Server Error')
+        lb1_request.finish()
+
+        # ...but succeed the second
+        lb2_request.setResponseCode(200)
+        lb2_request.setHeader('content-type', 'text/plain')
+        lb2_request.write(b'Yes, I work')
+        lb2_request.finish()
+
+        responses = yield d
+        self.assertThat(responses, HasLength(2))
+        lb1_response, lb2_response = responses
+
+        self.assertThat(lb1_response, Is(None))
+        self.assertThat(lb2_response, MatchesStructure(
+            code=Equals(200),
+            headers=HasHeader('content-type', ['text/plain'])
+        ))
+
+        lb2_response_content = yield lb2_response.content()
+        self.assertThat(lb2_response_content, Equals(b'Yes, I work'))
+
+        flush_logged_errors(HTTPError)
+
+    @inlineCallbacks
+    def test_request_failure(self):
+        """
+        When the requests to all the marathon-lb instances have a bad status
+        code then an error should be raised.
+        """
+        d = self.cleanup_d(self.client.request('GET', path='/my-path'))
+
+        for lb in ['lb1', 'lb2']:
+            request = yield self.requests.get()
+            self.assertThat(request, HasRequestProperties(
+                method='GET', url='http://%s:9090/my-path' % (lb,)))
+
+            request.setResponseCode(500)
+            request.setHeader('content-type', 'text/plain')
+            request.write(b'Internal Server Error')
+            request.finish()
+
+        yield wait0()
+        self.assertThat(d, failed(WithErrorTypeAndMessage(
+            RuntimeError,
+            'Failed to make a request to all marathon-lb instances'
+        )))
+
+        flush_logged_errors(HTTPError)
+
     @inlineCallbacks
     def test_mlb_signal_hup(self):
         """

pip install -e .[dev]
parse_log_pytest
requirements.txt
pytest flake8
apt-get update apt-get install -y gcc
3.9
requirements-dev.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
acme==0.40.1
attrs==25.3.0
Automat==24.8.1
boltons==25.0.0
certifi==2025.1.31
cffi==1.17.1
charset-normalizer==3.4.1
constantly==23.10.4
coverage==7.8.0
cryptography==44.0.2
eliot==1.17.5
exceptiongroup==1.2.2
fixtures==4.2.4.post1
flake8==7.2.0
hyperlink==21.0.0
idna==3.10
incremental==24.7.2
iniconfig==2.1.0
josepy==2.0.0
klein==15.3.1
-e git+https://github.com/praekeltfoundation/marathon-acme.git@cde0cf343338c7365f17455f895e70eb87590998#egg=marathon_acme
MarkupSafe==3.0.2
mccabe==0.7.0
mock==5.2.0
orjson==3.10.16
packaging==24.2
pem==23.1.0
pluggy==1.5.0
pyasn1==0.6.1
pyasn1_modules==0.4.2
pycodestyle==2.13.0
pycparser==2.22
pyflakes==3.3.1
pyOpenSSL==25.0.0
pyRFC3339==2.0.1
pyrsistent==0.20.0
pytest==8.3.5
pytest-cov==6.0.0
pytest-cover==3.0.0
pytest-coverage==0.0
pytz==2025.2
requests==2.32.3
requests-toolbelt==1.0.0
service-identity==24.2.0
six==1.17.0
testtools==2.7.2
tomli==2.2.1
treq==24.9.1
Twisted==24.11.0
txacme==0.9.3
txfake==0.1.1
TxSNI==0.2.0
typing_extensions==4.13.0
uritools==4.0.3
urllib3==2.3.0
Werkzeug==3.1.3
zope.interface==7.2

name: marathon-acme
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=3.0.16=h5eee18b_0
  - pip=25.0=py39h06a4308_0
  - python=3.9.21=he870216_1
  - readline=8.2=h5eee18b_0
  - setuptools=75.8.0=py39h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - tzdata=2025a=h04d1e81_0
  - wheel=0.45.1=py39h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - acme==0.40.1
      - attrs==25.3.0
      - automat==24.8.1
      - boltons==25.0.0
      - certifi==2025.1.31
      - cffi==1.17.1
      - charset-normalizer==3.4.1
      - constantly==23.10.4
      - coverage==7.8.0
      - cryptography==44.0.2
      - eliot==1.17.5
      - exceptiongroup==1.2.2
      - fixtures==4.2.4.post1
      - flake8==7.2.0
      - hyperlink==21.0.0
      - idna==3.10
      - incremental==24.7.2
      - iniconfig==2.1.0
      - josepy==2.0.0
      - klein==15.3.1
      - markupsafe==3.0.2
      - mccabe==0.7.0
      - mock==5.2.0
      - orjson==3.10.16
      - packaging==24.2
      - pem==23.1.0
      - pluggy==1.5.0
      - pyasn1==0.6.1
      - pyasn1-modules==0.4.2
      - pycodestyle==2.13.0
      - pycparser==2.22
      - pyflakes==3.3.1
      - pyopenssl==25.0.0
      - pyrfc3339==2.0.1
      - pyrsistent==0.20.0
      - pytest==8.3.5
      - pytest-cov==6.0.0
      - pytest-cover==3.0.0
      - pytest-coverage==0.0
      - pytz==2025.2
      - requests==2.32.3
      - requests-toolbelt==1.0.0
      - service-identity==24.2.0
      - six==1.17.0
      - testtools==2.7.2
      - tomli==2.2.1
      - treq==24.9.1
      - twisted==24.11.0
      - txacme==0.9.3
      - txfake==0.1.1
      - txsni==0.2.0
      - typing-extensions==4.13.0
      - uritools==4.0.3
      - urllib3==2.3.0
      - werkzeug==3.1.3
      - zope-interface==7.2
prefix: /opt/conda/envs/marathon-acme

marathon_acme/tests/test_clients.py::TestMarathonLbClient::test_request_failure marathon_acme/tests/test_clients.py::TestMarathonLbClient::test_request_partial_failure
marathon_acme/tests/test_clients.py::TestGetSingleHeader::test_single_value marathon_acme/tests/test_clients.py::TestGetSingleHeader::test_multiple_values marathon_acme/tests/test_clients.py::TestGetSingleHeader::test_value_with_params marathon_acme/tests/test_clients.py::TestGetSingleHeader::test_value_missing marathon_acme/tests/test_clients.py::TestDefaultReactor::test_default_reactor marathon_acme/tests/test_clients.py::TestDefaultReactor::test_default_reactor_not_provided marathon_acme/tests/test_clients.py::TestDefaultClient::test_default_client marathon_acme/tests/test_clients.py::TestDefaultClient::test_default_client_not_provided marathon_acme/tests/test_clients.py::TestHTTPClient::test_auth marathon_acme/tests/test_clients.py::TestHTTPClient::test_auth_precedence_over_url_userinfo marathon_acme/tests/test_clients.py::TestHTTPClient::test_client_error_response marathon_acme/tests/test_clients.py::TestHTTPClient::test_failure_during_request marathon_acme/tests/test_clients.py::TestHTTPClient::test_params marathon_acme/tests/test_clients.py::TestHTTPClient::test_params_precedence_over_url_query marathon_acme/tests/test_clients.py::TestHTTPClient::test_request marathon_acme/tests/test_clients.py::TestHTTPClient::test_request_debug_log marathon_acme/tests/test_clients.py::TestHTTPClient::test_request_url marathon_acme/tests/test_clients.py::TestHTTPClient::test_server_error_response marathon_acme/tests/test_clients.py::TestHTTPClient::test_url_overrides marathon_acme/tests/test_clients.py::TestHTTPClient::test_url_query_as_params marathon_acme/tests/test_clients.py::TestHTTPClient::test_url_userinfo_as_auth marathon_acme/tests/test_clients.py::TestJsonClient::test_json_content marathon_acme/tests/test_clients.py::TestJsonClient::test_json_content_incorrect_content_type marathon_acme/tests/test_clients.py::TestJsonClient::test_json_content_missing_content_type marathon_acme/tests/test_clients.py::TestJsonClient::test_json_data_and_data_not_allowed marathon_acme/tests/test_clients.py::TestJsonClient::test_request_json_data marathon_acme/tests/test_clients.py::TestMarathonClient::test_get_apps marathon_acme/tests/test_clients.py::TestMarathonClient::test_get_events marathon_acme/tests/test_clients.py::TestMarathonClient::test_get_events_incorrect_content_type marathon_acme/tests/test_clients.py::TestMarathonClient::test_get_events_multiple_callbacks marathon_acme/tests/test_clients.py::TestMarathonClient::test_get_events_multiple_events marathon_acme/tests/test_clients.py::TestMarathonClient::test_get_events_non_200 marathon_acme/tests/test_clients.py::TestMarathonClient::test_get_json_field marathon_acme/tests/test_clients.py::TestMarathonClient::test_get_json_field_error marathon_acme/tests/test_clients.py::TestMarathonClient::test_get_json_field_missing marathon_acme/tests/test_clients.py::TestMarathonLbClient::test_mlb_signal_hup marathon_acme/tests/test_clients.py::TestMarathonLbClient::test_mlb_signal_usr1 marathon_acme/tests/test_clients.py::TestMarathonLbClient::test_request_success
marathon_acme/clients.py
marathon_acme/clients.py