diff --git a/INSTALL.md b/INSTALL.md
index e496a13b2..fb6a5e4e9 100644
--- a/INSTALL.md
+++ b/INSTALL.md
@@ -350,18 +350,34 @@ Once you have installed synapse as above, you will need to configure it.
 
 ## TLS certificates
 
-The default configuration exposes two HTTP ports: 8008 and 8448. Port 8008 is
-configured without TLS; it should be behind a reverse proxy for TLS/SSL
-termination on port 443 which in turn should be used for clients. Port 8448
-is configured to use TLS for Federation with a self-signed or verified
-certificate, but please be aware that a valid certificate will be required in
-Synapse v1.0. Instructions for having Synapse automatically provision and renew federation certificates through ACME can be found at [ACME.md](docs/ACME.md).
-
-If you would like to use your own certificates, you can do so by changing
-`tls_certificate_path` and `tls_private_key_path` in `homeserver.yaml`;
-alternatively, you can use a reverse proxy.  See
-[docs/reverse_proxy.rst](docs/reverse_proxy.rst) for information on configuring
-a reverse proxy.
+The default configuration exposes a single HTTP port: http://localhost:8008. It
+is suitable for local testing, but for any practical use, you will either need
+to enable a reverse proxy, or configure Synapse to expose an HTTPS port.
+
+For information on using a reverse proxy, see
+[docs/reverse_proxy.rst](docs/reverse_proxy.rst).
+
+To configure Synapse to expose an HTTPS port, you will need to edit
+`homeserver.yaml`.
+
+First, under the `listeners` section, uncomment the configuration for the
+TLS-enabled listener. (Remove the hash sign (`#`) and space at the start of
+each line). The relevant lines are like this:
+
+```
+  - port: 8448
+    type: http
+    tls: true
+    resources:
+      - names: [client, federation]
+```
+
+You will also need to uncomment the `tls_certificate_path` and
+`tls_private_key_path` lines under the `TLS` section. You can either point
+these settings at an existing certificate and key, or you can enable Synapse's
+built-in ACME (Let's Encrypt) support.  Instructions for having Synapse
+automatically provision and renew federation certificates through ACME can be
+found at [ACME.md](docs/ACME.md).
 
 ## Registering a user
 
@@ -375,7 +391,7 @@ users. This can be done as follows:
 ```
 $ source ~/synapse/env/bin/activate
 $ synctl start # if not already running
-$ register_new_matrix_user -c homeserver.yaml https://localhost:8448
+$ register_new_matrix_user -c homeserver.yaml http://localhost:8008
 New user localpart: erikj
 Password:
 Confirm password:
diff --git a/README.rst b/README.rst
index bc7cb5f78..9a7c04b55 100644
--- a/README.rst
+++ b/README.rst
@@ -26,7 +26,6 @@ via IRC bridge at irc://irc.freenode.net/matrix.
 Synapse is currently in rapid development, but as of version 0.5 we believe it
 is sufficiently stable to be run as an internet-facing service for real usage!
 
-
 About Matrix
 ============
 
@@ -88,18 +87,20 @@ Connecting to Synapse from a client
 ===================================
 
 The easiest way to try out your new Synapse installation is by connecting to it
-from a web client. The easiest option is probably the one at
-https://riot.im/app. You will need to specify a "Custom server" when you log on
-or register: set this to ``https://domain.tld`` if you setup a reverse proxy
-following the recommended setup, or ``https://localhost:8448`` - remember to specify the
-port (``:8448``) if not ``:443`` unless you changed the configuration. (Leave the identity
-server as the default - see `Identity servers`_.)
-
-If using port 8448 you will run into errors if you are using a self-signed
-certificate. To overcome this, simply go to ``https://localhost:8448``
-directly with your browser and accept the presented certificate. You can then
-go back in your web client and proceed further. Valid federation certificates
-should not have this problem.
+from a web client.
+
+Unless you are running a test instance of Synapse on your local machine, in
+general, you will need to enable TLS support before you can successfully
+connect from a client: see `<INSTALL.md#tls-certificates>`_.
+
+An easy way to get started is to login or register via Riot at 
+https://riot.im/app/#/login or https://riot.im/app/#/register respectively. 
+You will need to change the server you are logging into from ``matrix.org``
+and instead specify a Homeserver URL of ``https://<server_name>:8448`` 
+(or just ``https://<server_name>`` if you are using a reverse proxy). 
+(Leave the identity server as the default - see `Identity servers`_.) 
+If you prefer to use another client, refer to our 
+`client breakdown <https://matrix.org/docs/projects/clients-matrix>`_.
 
 If all goes well you should at least be able to log in, create a room, and
 start sending messages.
@@ -174,9 +175,30 @@ Separately, Synapse may leak file handles if inbound HTTP requests get stuck
 during processing - e.g. blocked behind a lock or talking to a remote server etc.
 This is best diagnosed by matching up the 'Received request' and 'Processed request'
 log lines and looking for any 'Processed request' lines which take more than
-a few seconds to execute.  Please let us know at #matrix-dev:matrix.org if
+a few seconds to execute.  Please let us know at #synapse:matrix.org if
 you see this failure mode so we can help debug it, however.
 
+Help!! Synapse eats all my RAM!
+-------------------------------
+
+Synapse's architecture is quite RAM hungry currently - we deliberately
+cache a lot of recent room data and metadata in RAM in order to speed up
+common requests.  We'll improve this in future, but for now the easiest
+way to either reduce the RAM usage (at the risk of slowing things down)
+is to set the almost-undocumented ``SYNAPSE_CACHE_FACTOR`` environment
+variable.  The default is 0.5, which can be decreased to reduce RAM usage
+in memory constrained enviroments, or increased if performance starts to
+degrade.
+
+Using `libjemalloc <http://jemalloc.net/>`_ can also yield a significant
+improvement in overall amount, and especially in terms of giving back RAM
+to the OS. To use it, the library must simply be put in the LD_PRELOAD
+environment variable when launching Synapse. On Debian, this can be done
+by installing the ``libjemalloc1`` package and adding this line to
+``/etc/default/matrix-synapse``::
+
+    LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libjemalloc.so.1
+
 
 Upgrading an existing Synapse
 =============================
@@ -196,12 +218,12 @@ Federation is the process by which users on different servers can participate
 in the same room. For this to work, those other servers must be able to contact
 yours to send messages.
 
-The ``server_name`` in your
-``homeserver.yaml`` file determines the way that other servers will reach
-yours. By default, they will treat it as a hostname and try to connect to
-port 8448. This is easy to set up and will work with the default configuration,
-provided you set the ``server_name`` to match your machine's public DNS
-hostname.
+The ``server_name`` in your ``homeserver.yaml`` file determines the way that
+other servers will reach yours. By default, they will treat it as a hostname
+and try to connect to port 8448. This is easy to set up and will work with the
+default configuration, provided you set the ``server_name`` to match your
+machine's public DNS hostname, and give Synapse a TLS certificate which is
+valid for your ``server_name``.
 
 For a more flexible configuration, you can set up a DNS SRV record. This allows
 you to run your server on a machine that might not have the same name as your
@@ -243,11 +265,8 @@ largest boxes pause for thought.)
 Troubleshooting
 ---------------
 
-You can use the federation tester to check if your homeserver is all set:
-``https://matrix.org/federationtester/api/report?server_name=<your_server_name>``
-If any of the attributes under "checks" is false, federation won't work.
-There is also a nicer interface available from a community member at
-`<https://neo.lain.haus/fed-tester>`_.
+You can use the `federation tester <https://matrix.org/federationtester>`_ to
+check if your homeserver is all set.
 
 The typical failure mode with federation is that when you try to join a room,
 it is rejected with "401: Unauthorized". Generally this means that other
@@ -263,7 +282,10 @@ So, things to check are:
   (it should be ``_matrix._tcp.<server_name>``), and that the port and hostname
   it specifies are reachable from outside your network.
 
-.. TODO: add a note about forgetting ``nocanon`` on a reverse-proxy config
+Another common problem is that people on other servers can't join rooms that
+you invite them to. This can be caused by an incorrectly-configured reverse
+proxy: see `<docs/reverse_proxy.rst>`_ for instructions on how to correctly
+configure a reverse proxy.
 
 Running a Demo Federation of Synapses
 -------------------------------------
@@ -363,7 +385,7 @@ Synapse Development
 
 Before setting up a development environment for synapse, make sure you have the
 system dependencies (such as the python header files) installed - see
-`Installing from source`_.
+`Installing from source <INSTALL.md#installing-from-source>`_.
 
 To check out a synapse for development, clone the git repo into a working
 directory of your choice::
@@ -374,7 +396,7 @@ directory of your choice::
 Synapse has a number of external dependencies, that are easiest
 to install using pip and a virtualenv::
 
-    virtualenv -p python2.7 env
+    virtualenv -p python3 env
     source env/bin/activate
     python -m pip install -e .[all]
 
@@ -416,25 +438,3 @@ sphinxcontrib-napoleon::
 Building internal API documentation::
 
     python setup.py build_sphinx
-
-
-Help!! Synapse eats all my RAM!
-===============================
-
-Synapse's architecture is quite RAM hungry currently - we deliberately
-cache a lot of recent room data and metadata in RAM in order to speed up
-common requests.  We'll improve this in future, but for now the easiest
-way to either reduce the RAM usage (at the risk of slowing things down)
-is to set the almost-undocumented ``SYNAPSE_CACHE_FACTOR`` environment
-variable.  The default is 0.5, which can be decreased to reduce RAM usage
-in memory constrained enviroments, or increased if performance starts to
-degrade.
-
-Using `libjemalloc <http://jemalloc.net/>`_ can also yield a significant
-improvement in overall amount, and especially in terms of giving back RAM
-to the OS. To use it, the library must simply be put in the LD_PRELOAD
-environment variable when launching Synapse. On Debian, this can be done
-by installing the ``libjemalloc1`` package and adding this line to
-``/etc/default/matrix-synapse``::
-
-    LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libjemalloc.so.1
diff --git a/changelog.d/4613.feature b/changelog.d/4613.feature
new file mode 100644
index 000000000..098f906af
--- /dev/null
+++ b/changelog.d/4613.feature
@@ -0,0 +1,1 @@
+There is no longer any need to specify `no_tls`: it is inferred from the absence of TLS listeners
diff --git a/changelog.d/4614.feature b/changelog.d/4614.feature
new file mode 100644
index 000000000..18e16dbc7
--- /dev/null
+++ b/changelog.d/4614.feature
@@ -0,0 +1,1 @@
+The default configuration no longer requires TLS certificates.
diff --git a/changelog.d/4615.feature b/changelog.d/4615.feature
new file mode 100644
index 000000000..098f906af
--- /dev/null
+++ b/changelog.d/4615.feature
@@ -0,0 +1,1 @@
+There is no longer any need to specify `no_tls`: it is inferred from the absence of TLS listeners
diff --git a/changelog.d/4616.misc b/changelog.d/4616.misc
new file mode 100644
index 000000000..ee79e208e
--- /dev/null
+++ b/changelog.d/4616.misc
@@ -0,0 +1,1 @@
+Fail cleanly if listener config lacks a 'port'
diff --git a/changelog.d/4617.feature b/changelog.d/4617.feature
new file mode 100644
index 000000000..098f906af
--- /dev/null
+++ b/changelog.d/4617.feature
@@ -0,0 +1,1 @@
+There is no longer any need to specify `no_tls`: it is inferred from the absence of TLS listeners
diff --git a/changelog.d/4618.bugfix b/changelog.d/4618.bugfix
new file mode 100644
index 000000000..22115a020
--- /dev/null
+++ b/changelog.d/4618.bugfix
@@ -0,0 +1,1 @@
+Fix failure to start when not TLS certificate was given even if TLS was disabled.
diff --git a/changelog.d/4619.misc b/changelog.d/4619.misc
new file mode 100644
index 000000000..886fedf19
--- /dev/null
+++ b/changelog.d/4619.misc
@@ -0,0 +1,1 @@
+Remove redundant entries from docker config
diff --git a/changelog.d/4621.misc b/changelog.d/4621.misc
new file mode 100644
index 000000000..60e45cb70
--- /dev/null
+++ b/changelog.d/4621.misc
@@ -0,0 +1,1 @@
+README updates
diff --git a/docker/conf/dummy.tls.crt b/docker/conf/dummy.tls.crt
deleted file mode 100644
index 8e3b1a9aa..000000000
--- a/docker/conf/dummy.tls.crt
+++ /dev/null
@@ -1,17 +0,0 @@
------BEGIN CERTIFICATE-----
-MIICnTCCAYUCAgPoMA0GCSqGSIb3DQEBCwUAMBQxEjAQBgNVBAMMCWxvY2FsaG9z
-dDAeFw0xOTAxMTUwMDQxNTBaFw0yOTAxMTIwMDQxNTBaMBQxEjAQBgNVBAMMCWxv
-Y2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMKqm81/8j5d
-R1s7VZ8ueg12gJrPVCCAOkp0UnuC/ZlXhN0HTvnhQ+B0IlSgB4CcQZyf4jnA6o4M
-rwSc7VX0MPE9x/idoA0g/0WoC6tsxugOrvbzCw8Tv+fnXglm6uVc7aFPfx69wU3q
-lUHGD/8jtEoHxmCG177Pt2lHAfiVLBAyMQGtETzxt/yAfkloaybe316qoljgK5WK
-cokdAt9G84EEqxNeEnx5FG3Vc100bAqJS4GvQlFgtF9KFEqZKEyB1yKBpPMDfPIS
-V9hIV0gswSmYI8dpyBlGf5lPElY68ZGABmOQgr0RI5qHK/h28OpFPE0q3v4AMHgZ
-I36wii4NrAUCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAfD8kcpZ+dn08xh1qtKtp
-X+/YNZaOBIeVdlCzfoZKNblSFAFD/jCfObNJYvZMUQ8NX2UtEJp1lTA6m7ltSsdY
-gpC2k1VD8iN+ooXklJmL0kxc7UUqho8I0l9vn35h+lhLF0ihT6XfZVi/lDHWl+4G
-rG+v9oxvCSCWrNWLearSlFPtQQ8xPtOE0nLwfXtOI/H/2kOuC38ihaIWM4jjbWXK
-E/ksgUfuDv0mFiwf1YdBF5/M3/qOowqzU8HgMJ3WoT/9Po5Ya1pWc+3BcxxytUDf
-XdMu0tWHKX84tZxLcR1nZHzluyvFFM8xNtLi9xV0Z7WbfT76V0C/ulEOybGInYsv
-nQ==
------END CERTIFICATE-----
diff --git a/docker/conf/homeserver.yaml b/docker/conf/homeserver.yaml
index f07d5c100..babd5bef9 100644
--- a/docker/conf/homeserver.yaml
+++ b/docker/conf/homeserver.yaml
@@ -2,13 +2,7 @@
 
 ## TLS ##
 
-{% if SYNAPSE_NO_TLS %}
-no_tls: True
-
-# workaround for https://github.com/matrix-org/synapse/issues/4554
-tls_certificate_path: "/conf/dummy.tls.crt"
-
-{% else %}
+{% if not SYNAPSE_NO_TLS %}
 
 tls_certificate_path: "/data/{{ SYNAPSE_SERVER_NAME }}.tls.crt"
 tls_private_key_path: "/data/{{ SYNAPSE_SERVER_NAME }}.tls.key"
diff --git a/synapse/app/_base.py b/synapse/app/_base.py
index e1fc1afd5..5b0ca312e 100644
--- a/synapse/app/_base.py
+++ b/synapse/app/_base.py
@@ -213,13 +213,17 @@ def refresh_certificate(hs):
     Refresh the TLS certificates that Synapse is using by re-reading them from
     disk and updating the TLS context factories to use them.
     """
-    logging.info("Loading certificate from disk...")
-    hs.config.read_certificate_from_disk()
+
+    if not hs.config.has_tls_listener():
+        # attempt to reload the certs for the good of the tls_fingerprints
+        hs.config.read_certificate_from_disk(require_cert_and_key=False)
+        return
+
+    hs.config.read_certificate_from_disk(require_cert_and_key=True)
     hs.tls_server_context_factory = context_factory.ServerContextFactory(hs.config)
-    logging.info("Certificate loaded.")
 
     if hs._listening_services:
-        logging.info("Updating context factories...")
+        logger.info("Updating context factories...")
         for i in hs._listening_services:
             # When you listenSSL, it doesn't make an SSL port but a TCP one with
             # a TLS wrapping factory around the factory you actually want to get
@@ -234,7 +238,7 @@ def refresh_certificate(hs):
                     False,
                     i.factory.wrappedFactory
                 )
-        logging.info("Context factories updated.")
+        logger.info("Context factories updated.")
 
 
 def start(hs, listeners=None):
diff --git a/synapse/app/homeserver.py b/synapse/app/homeserver.py
index b4476bf16..dbd98d394 100755
--- a/synapse/app/homeserver.py
+++ b/synapse/app/homeserver.py
@@ -90,11 +90,6 @@ class SynapseHomeServer(HomeServer):
         tls = listener_config.get("tls", False)
         site_tag = listener_config.get("tag", port)
 
-        if tls and config.no_tls:
-            raise ConfigError(
-                "Listener on port %i has TLS enabled, but no_tls is set" % (port,),
-            )
-
         resources = {}
         for res in listener_config["resources"]:
             for name in res["names"]:
diff --git a/synapse/config/homeserver.py b/synapse/config/homeserver.py
index 5aad062c3..727fdc54d 100644
--- a/synapse/config/homeserver.py
+++ b/synapse/config/homeserver.py
@@ -42,7 +42,7 @@ from .voip import VoipConfig
 from .workers import WorkerConfig
 
 
-class HomeServerConfig(TlsConfig, ServerConfig, DatabaseConfig, LoggingConfig,
+class HomeServerConfig(ServerConfig, TlsConfig, DatabaseConfig, LoggingConfig,
                        RatelimitConfig, ContentRepositoryConfig, CaptchaConfig,
                        VoipConfig, RegistrationConfig, MetricsConfig, ApiConfig,
                        AppServiceConfig, KeyConfig, SAML2Config, CasConfig,
diff --git a/synapse/config/server.py b/synapse/config/server.py
index ce0458195..c5c3aac8e 100644
--- a/synapse/config/server.py
+++ b/synapse/config/server.py
@@ -126,9 +126,22 @@ class ServerConfig(Config):
                 self.public_baseurl += '/'
         self.start_pushers = config.get("start_pushers", True)
 
-        self.listeners = config.get("listeners", [])
+        self.listeners = []
+        for listener in config.get("listeners", []):
+            if not isinstance(listener.get("port", None), int):
+                raise ConfigError(
+                    "Listener configuration is lacking a valid 'port' option"
+                )
+
+            if listener.setdefault("tls", False):
+                # no_tls is not really supported any more, but let's grandfather it in
+                # here.
+                if config.get("no_tls", False):
+                    logger.info(
+                        "Ignoring TLS-enabled listener on port %i due to no_tls"
+                    )
+                    continue
 
-        for listener in self.listeners:
             bind_address = listener.pop("bind_address", None)
             bind_addresses = listener.setdefault("bind_addresses", [])
 
@@ -140,6 +153,8 @@ class ServerConfig(Config):
             if not bind_addresses:
                 bind_addresses.extend(DEFAULT_BIND_ADDRESSES)
 
+            self.listeners.append(listener)
+
         if not self.web_client_location:
             _warn_if_webclient_configured(self.listeners)
 
@@ -147,6 +162,9 @@ class ServerConfig(Config):
 
         bind_port = config.get("bind_port")
         if bind_port:
+            if config.get("no_tls", False):
+                raise ConfigError("no_tls is incompatible with bind_port")
+
             self.listeners = []
             bind_host = config.get("bind_host", "")
             gzip_responses = config.get("gzip_responses", True)
@@ -193,6 +211,7 @@ class ServerConfig(Config):
                 "port": manhole,
                 "bind_addresses": ["127.0.0.1"],
                 "type": "manhole",
+                "tls": False,
             })
 
         metrics_port = config.get("metrics_port")
@@ -218,6 +237,9 @@ class ServerConfig(Config):
 
         _check_resource_config(self.listeners)
 
+    def has_tls_listener(self):
+        return any(l["tls"] for l in self.listeners)
+
     def default_config(self, server_name, data_dir_path, **kwargs):
         _, bind_port = parse_and_validate_server_name(server_name)
         if bind_port is not None:
@@ -365,47 +387,47 @@ class ServerConfig(Config):
         #   webclient: A web client. Requires web_client_location to be set.
         #
         listeners:
-          # Main HTTPS listener.
-          # For when matrix traffic is sent directly to synapse.
-          - port: %(bind_port)s
+          # TLS-enabled listener: for when matrix traffic is sent directly to synapse.
+          #
+          # Disabled by default. To enable it, uncomment the following. (Note that you
+          # will also need to give Synapse a TLS key and certificate: see the TLS section
+          # below.)
+          #
+          # - port: %(bind_port)s
+          #   type: http
+          #   tls: true
+          #   resources:
+          #     - names: [client, federation]
+
+          # Unsecure HTTP listener: for when matrix traffic passes through a reverse proxy
+          # that unwraps TLS.
+          #
+          # If you plan to use a reverse proxy, please see
+          # https://github.com/matrix-org/synapse/blob/master/docs/reverse_proxy.rst.
+          #
+          - port: %(unsecure_port)s
+            tls: false
+            bind_addresses: ['::1', '127.0.0.1']
             type: http
-            tls: true
+            x_forwarded: true
 
-            # List of HTTP resources to serve on this listener.
             resources:
-              - names: [client]
-                compress: true
-              - names: [federation]
+              - names: [client, federation]
                 compress: false
 
-            # example addional_resources:
+            # example additonal_resources:
             #
             # additional_resources:
             #   "/_matrix/my/custom/endpoint":
             #     module: my_module.CustomRequestHandler
             #     config: {}
 
-          # Unsecure HTTP listener
-          # For when matrix traffic passes through a reverse-proxy that unwraps TLS.
-          - port: %(unsecure_port)s
-            tls: false
-            bind_addresses: ['::1', '127.0.0.1']
-            type: http
-            x_forwarded: true
-
-            resources:
-              - names: [client]
-                compress: true
-              - names: [federation]
-                compress: false
-
           # Turn on the twisted ssh manhole service on localhost on the given
           # port.
           # - port: 9000
           #   bind_addresses: ['::1', '127.0.0.1']
           #   type: manhole
 
-
         # Homeserver blocking
         #
         # How to reach the server admin, used in ResourceLimitError
diff --git a/synapse/config/tls.py b/synapse/config/tls.py
index 9fcc79816..5fb3486db 100644
--- a/synapse/config/tls.py
+++ b/synapse/config/tls.py
@@ -23,9 +23,9 @@ from unpaddedbase64 import encode_base64
 
 from OpenSSL import crypto
 
-from synapse.config._base import Config
+from synapse.config._base import Config, ConfigError
 
-logger = logging.getLogger()
+logger = logging.getLogger(__name__)
 
 
 class TlsConfig(Config):
@@ -45,13 +45,25 @@ class TlsConfig(Config):
 
         self.tls_certificate_file = self.abspath(config.get("tls_certificate_path"))
         self.tls_private_key_file = self.abspath(config.get("tls_private_key_path"))
+
+        if self.has_tls_listener():
+            if not self.tls_certificate_file:
+                raise ConfigError(
+                    "tls_certificate_path must be specified if TLS-enabled listeners are "
+                    "configured."
+                )
+            if not self.tls_private_key_file:
+                raise ConfigError(
+                    "tls_certificate_path must be specified if TLS-enabled listeners are "
+                    "configured."
+                )
+
         self._original_tls_fingerprints = config.get("tls_fingerprints", [])
 
         if self._original_tls_fingerprints is None:
             self._original_tls_fingerprints = []
 
         self.tls_fingerprints = list(self._original_tls_fingerprints)
-        self.no_tls = config.get("no_tls", False)
 
         # This config option applies to non-federation HTTP clients
         # (e.g. for talking to recaptcha, identity servers, and such)
@@ -106,36 +118,40 @@ class TlsConfig(Config):
         days_remaining = (expires_on - now).days
         return days_remaining
 
-    def read_certificate_from_disk(self):
-        """
-        Read the certificates from disk.
+    def read_certificate_from_disk(self, require_cert_and_key):
         """
-        self.tls_certificate = self.read_tls_certificate(self.tls_certificate_file)
+        Read the certificates and private key from disk.
 
-        # Check if it is self-signed, and issue a warning if so.
-        if self.tls_certificate.get_issuer() == self.tls_certificate.get_subject():
-            warnings.warn(
-                (
-                    "Self-signed TLS certificates will not be accepted by Synapse 1.0. "
-                    "Please either provide a valid certificate, or use Synapse's ACME "
-                    "support to provision one."
+        Args:
+            require_cert_and_key (bool): set to True to throw an error if the certificate
+                and key file are not given
+        """
+        if require_cert_and_key:
+            self.tls_private_key = self.read_tls_private_key()
+            self.tls_certificate = self.read_tls_certificate()
+        elif self.tls_certificate_file:
+            # we only need the certificate for the tls_fingerprints. Reload it if we
+            # can, but it's not a fatal error if we can't.
+            try:
+                self.tls_certificate = self.read_tls_certificate()
+            except Exception as e:
+                logger.info(
+                    "Unable to read TLS certificate (%s). Ignoring as no "
+                    "tls listeners enabled.", e,
                 )
-            )
-
-        if not self.no_tls:
-            self.tls_private_key = self.read_tls_private_key(self.tls_private_key_file)
 
         self.tls_fingerprints = list(self._original_tls_fingerprints)
 
-        # Check that our own certificate is included in the list of fingerprints
-        # and include it if it is not.
-        x509_certificate_bytes = crypto.dump_certificate(
-            crypto.FILETYPE_ASN1, self.tls_certificate
-        )
-        sha256_fingerprint = encode_base64(sha256(x509_certificate_bytes).digest())
-        sha256_fingerprints = set(f["sha256"] for f in self.tls_fingerprints)
-        if sha256_fingerprint not in sha256_fingerprints:
-            self.tls_fingerprints.append({u"sha256": sha256_fingerprint})
+        if self.tls_certificate:
+            # Check that our own certificate is included in the list of fingerprints
+            # and include it if it is not.
+            x509_certificate_bytes = crypto.dump_certificate(
+                crypto.FILETYPE_ASN1, self.tls_certificate
+            )
+            sha256_fingerprint = encode_base64(sha256(x509_certificate_bytes).digest())
+            sha256_fingerprints = set(f["sha256"] for f in self.tls_fingerprints)
+            if sha256_fingerprint not in sha256_fingerprints:
+                self.tls_fingerprints.append({u"sha256": sha256_fingerprint})
 
     def default_config(self, config_dir_path, server_name, **kwargs):
         base_key_name = os.path.join(config_dir_path, server_name)
@@ -151,6 +167,8 @@ class TlsConfig(Config):
 
         return (
             """\
+        ## TLS ##
+
         # PEM-encoded X509 certificate for TLS.
         # This certificate, as of Synapse 1.0, will need to be a valid and verifiable
         # certificate, signed by a recognised Certificate Authority.
@@ -158,10 +176,10 @@ class TlsConfig(Config):
         # See 'ACME support' below to enable auto-provisioning this certificate via
         # Let's Encrypt.
         #
-        tls_certificate_path: "%(tls_certificate_path)s"
+        # tls_certificate_path: "%(tls_certificate_path)s"
 
         # PEM-encoded private key for TLS
-        tls_private_key_path: "%(tls_private_key_path)s"
+        # tls_private_key_path: "%(tls_private_key_path)s"
 
         # ACME support: This will configure Synapse to request a valid TLS certificate
         # for your configured `server_name` via Let's Encrypt.
@@ -186,7 +204,7 @@ class TlsConfig(Config):
         #
         acme:
             # ACME support is disabled by default. Uncomment the following line
-            # to enable it.
+            # (and tls_certificate_path and tls_private_key_path above) to enable it.
             #
             # enabled: true
 
@@ -211,13 +229,6 @@ class TlsConfig(Config):
             #
             # reprovision_threshold: 30
 
-        # If your server runs behind a reverse-proxy which terminates TLS connections
-        # (for both client and federation connections), it may be useful to disable
-        # All TLS support for incoming connections. Setting no_tls to True will
-        # do so (and avoid the need to give synapse a TLS private key).
-        #
-        # no_tls: True
-
         # List of allowed TLS fingerprints for this server to publish along
         # with the signing keys for this server. Other matrix servers that
         # make HTTPS requests to this server will check that the TLS
@@ -250,10 +261,38 @@ class TlsConfig(Config):
             % locals()
         )
 
-    def read_tls_certificate(self, cert_path):
-        cert_pem = self.read_file(cert_path, "tls_certificate")
-        return crypto.load_certificate(crypto.FILETYPE_PEM, cert_pem)
+    def read_tls_certificate(self):
+        """Reads the TLS certificate from the configured file, and returns it
+
+        Also checks if it is self-signed, and warns if so
+
+        Returns:
+            OpenSSL.crypto.X509: the certificate
+        """
+        cert_path = self.tls_certificate_file
+        logger.info("Loading TLS certificate from %s", cert_path)
+        cert_pem = self.read_file(cert_path, "tls_certificate_path")
+        cert = crypto.load_certificate(crypto.FILETYPE_PEM, cert_pem)
+
+        # Check if it is self-signed, and issue a warning if so.
+        if cert.get_issuer() == cert.get_subject():
+            warnings.warn(
+                (
+                    "Self-signed TLS certificates will not be accepted by Synapse 1.0. "
+                    "Please either provide a valid certificate, or use Synapse's ACME "
+                    "support to provision one."
+                )
+            )
+
+        return cert
+
+    def read_tls_private_key(self):
+        """Reads the TLS private key from the configured file, and returns it
 
-    def read_tls_private_key(self, private_key_path):
-        private_key_pem = self.read_file(private_key_path, "tls_private_key")
+        Returns:
+            OpenSSL.crypto.PKey: the private key
+        """
+        private_key_path = self.tls_private_key_file
+        logger.info("Loading TLS key from %s", private_key_path)
+        private_key_pem = self.read_file(private_key_path, "tls_private_key_path")
         return crypto.load_privatekey(crypto.FILETYPE_PEM, private_key_pem)
diff --git a/synapse/crypto/context_factory.py b/synapse/crypto/context_factory.py
index 286ad8010..85f2848fb 100644
--- a/synapse/crypto/context_factory.py
+++ b/synapse/crypto/context_factory.py
@@ -43,9 +43,7 @@ class ServerContextFactory(ContextFactory):
             logger.exception("Failed to enable elliptic curve for TLS")
         context.set_options(SSL.OP_NO_SSLv2 | SSL.OP_NO_SSLv3)
         context.use_certificate_chain_file(config.tls_certificate_file)
-
-        if not config.no_tls:
-            context.use_privatekey(config.tls_private_key)
+        context.use_privatekey(config.tls_private_key)
 
         # https://hynek.me/articles/hardening-your-web-servers-ssl-ciphers/
         context.set_cipher_list(

Synapse Development instructions need updating
They need to be updated to use Python 3 and point at the new INSTALL.md
diff --git a/tests/config/test_tls.py b/tests/config/test_tls.py
index 4ccaf3560..c260d3359 100644
--- a/tests/config/test_tls.py
+++ b/tests/config/test_tls.py
@@ -20,6 +20,11 @@ from synapse.config.tls import TlsConfig
 from tests.unittest import TestCase
 
 
+class TestConfig(TlsConfig):
+    def has_tls_listener(self):
+        return False
+
+
 class TLSConfigTests(TestCase):
 
     def test_warn_self_signed(self):
@@ -55,13 +60,12 @@ s4niecZKPBizL6aucT59CsunNmmb5Glq8rlAcU+1ZTZZzGYqVYhF6axB9Qg=
 
         config = {
             "tls_certificate_path": os.path.join(config_dir, "cert.pem"),
-            "no_tls": True,
             "tls_fingerprints": []
         }
 
-        t = TlsConfig()
+        t = TestConfig()
         t.read_config(config)
-        t.read_certificate_from_disk()
+        t.read_certificate_from_disk(require_cert_and_key=False)
 
         warnings = self.flushWarnings()
         self.assertEqual(len(warnings), 1)

pip install -e .[all]
parse_log_pytest
requirements.txt
coverage mock pytest
apt-get update apt-get install -y build-essential python3-dev libffi-dev
3.6
requirements/base.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
acme==0.40.1
aiocontextvars==0.2.2
attrs==22.2.0
Automat==22.10.0
bcrypt==4.0.1
bleach==4.1.0
boltons==23.1.1
canonicaljson==1.5.0
certifi==2021.5.30
cffi==1.15.1
charset-normalizer==2.0.12
constantly==15.1.0
contextvars==2.4
coverage==6.2
cryptography==40.0.2
daemonize==2.5.0
defusedxml==0.7.1
elementpath==2.4.0
eliot==1.14.0
frozendict==2.4.6
hyperlink==21.0.0
idna==3.10
immutables==0.19
importlib-metadata==4.8.3
importlib-resources==5.4.0
incremental==22.10.0
iniconfig==1.1.1
Jinja2==3.0.3
josepy==1.13.0
jsonschema==3.2.0
ldap3==2.9.1
lxml==5.3.1
MarkupSafe==2.0.1
-e git+https://github.com/matrix-org/synapse.git@5d27730a73d01bd3ff7eb42a21f2f024493c5b89#egg=matrix_synapse
matrix-synapse-ldap3==0.1.5
mock==5.2.0
msgpack==1.0.5
netaddr==0.10.1
packaging==21.3
parameterized==0.8.1
pem==21.2.0
phonenumbers==9.0.2
Pillow==8.4.0
pluggy==1.0.0
prometheus-client==0.3.1
psutil==7.0.0
psycopg2==2.7.7
py==1.11.0
pyasn1==0.5.1
pyasn1-modules==0.3.0
pycparser==2.21
pymacaroons==0.13.0
PyNaCl==1.5.0
pyOpenSSL==23.2.0
pyparsing==3.1.4
pyRFC3339==2.0.1
pyrsistent==0.18.0
pysaml2==7.3.1
pytest==7.0.1
python-dateutil==2.9.0.post0
pytz==2025.2
PyYAML==6.0.1
requests==2.27.1
requests-toolbelt==1.0.0
service-identity==21.1.0
signedjson==1.1.4
simplejson==3.20.1
six==1.17.0
sortedcontainers==2.4.0
tomli==1.2.3
treq==22.2.0
Twisted==22.4.0
txacme==0.9.3
TxSNI==0.2.0
typing_extensions==4.1.1
unpaddedbase64==2.1.0
urllib3==1.26.20
webencodings==0.5.1
xmlschema==1.9.2
zipp==3.6.0
zope.interface==5.5.2

name: synapse
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
  - certifi=2021.5.30=py36h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.3=he6710b0_2
  - 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=1.1.1w=h7f8727e_0
  - pip=21.2.2=py36h06a4308_0
  - python=3.6.13=h12debd9_1
  - readline=8.2=h5eee18b_0
  - setuptools=58.0.4=py36h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - wheel=0.37.1=pyhd3eb1b0_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - acme==0.40.1
      - aiocontextvars==0.2.2
      - attrs==22.2.0
      - automat==22.10.0
      - bcrypt==4.0.1
      - bleach==4.1.0
      - boltons==23.1.1
      - canonicaljson==1.5.0
      - cffi==1.15.1
      - charset-normalizer==2.0.12
      - constantly==15.1.0
      - contextvars==2.4
      - coverage==6.2
      - cryptography==40.0.2
      - daemonize==2.5.0
      - defusedxml==0.7.1
      - elementpath==2.4.0
      - eliot==1.14.0
      - frozendict==2.4.6
      - hyperlink==21.0.0
      - idna==3.10
      - immutables==0.19
      - importlib-metadata==4.8.3
      - importlib-resources==5.4.0
      - incremental==22.10.0
      - iniconfig==1.1.1
      - jinja2==3.0.3
      - josepy==1.13.0
      - jsonschema==3.2.0
      - ldap3==2.9.1
      - lxml==5.3.1
      - markupsafe==2.0.1
      - matrix-synapse-ldap3==0.1.5
      - mock==5.2.0
      - msgpack==1.0.5
      - netaddr==0.10.1
      - packaging==21.3
      - parameterized==0.8.1
      - pem==21.2.0
      - phonenumbers==9.0.2
      - pillow==8.4.0
      - pluggy==1.0.0
      - prometheus-client==0.3.1
      - psutil==7.0.0
      - psycopg2==2.7.7
      - py==1.11.0
      - pyasn1==0.5.1
      - pyasn1-modules==0.3.0
      - pycparser==2.21
      - pymacaroons==0.13.0
      - pynacl==1.5.0
      - pyopenssl==23.2.0
      - pyparsing==3.1.4
      - pyrfc3339==2.0.1
      - pyrsistent==0.18.0
      - pysaml2==7.3.1
      - pytest==7.0.1
      - python-dateutil==2.9.0.post0
      - pytz==2025.2
      - pyyaml==6.0.1
      - requests==2.27.1
      - requests-toolbelt==1.0.0
      - service-identity==21.1.0
      - signedjson==1.1.4
      - simplejson==3.20.1
      - six==1.17.0
      - sortedcontainers==2.4.0
      - tomli==1.2.3
      - treq==22.2.0
      - twisted==22.4.0
      - txacme==0.9.3
      - txsni==0.2.0
      - typing-extensions==4.1.1
      - unpaddedbase64==2.1.0
      - urllib3==1.26.20
      - webencodings==0.5.1
      - xmlschema==1.9.2
      - zipp==3.6.0
      - zope-interface==5.5.2
prefix: /opt/conda/envs/synapse

tests/config/test_tls.py::TLSConfigTests::test_warn_self_signed
README.rst changelog.d/4614.feature changelog.d/4616.misc INSTALL.md changelog.d/4618.bugfix changelog.d/4613.feature changelog.d/4619.misc docker/conf/dummy.tls.crt docker/conf/homeserver.yaml synapse/app/_base.py synapse/app/homeserver.py synapse/config/tls.py synapse/crypto/context_factory.py synapse/config/homeserver.py changelog.d/4615.feature changelog.d/4621.misc synapse/config/server.py changelog.d/4617.feature
README.rst changelog.d/4614.feature changelog.d/4616.misc INSTALL.md changelog.d/4618.bugfix changelog.d/4613.feature changelog.d/4619.misc docker/conf/dummy.tls.crt docker/conf/homeserver.yaml synapse/app/_base.py synapse/app/homeserver.py synapse/config/tls.py synapse/crypto/context_factory.py synapse/config/homeserver.py changelog.d/4615.feature changelog.d/4621.misc synapse/config/server.py changelog.d/4617.feature