diff --git a/src/leap/soledad/common/couch/check.py b/src/leap/soledad/common/couch/check.py
new file mode 100644
index 0000000000000000000000000000000000000000..f55dd6f6102a369cca2aa45c5c89d96d98db0579
--- /dev/null
+++ b/src/leap/soledad/common/couch/check.py
@@ -0,0 +1,123 @@
+# -*- coding: utf-8 -*-
+# check.py
+# Copyright (C) 2015,2016 LEAP
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+"""
+Database schema version verification
+"""
+
+import os
+import treq
+
+from six.moves.urllib.parse import urljoin
+from twisted.internet import defer
+from urlparse import urlsplit
+
+from twisted.internet import reactor
+
+from leap.soledad.common.couch import CONFIG_DOC_ID
+from leap.soledad.common.couch import SCHEMA_VERSION
+from leap.soledad.common.couch import SCHEMA_VERSION_KEY
+from leap.soledad.common.errors import WrongCouchSchemaVersionError
+from leap.soledad.common.errors import MissingCouchConfigDocumentError
+from leap.soledad.common.log import getLogger
+
+
+logger = getLogger(__name__)
+
+
+@defer.inlineCallbacks
+def _check_db_schema_version(url, db, auth, agent=None):
+    """
+    Check if the schema version is up to date for a given database.
+
+    :param url: the server base URL.
+    :type url: str
+    :param db: the database name.
+    :type db: str
+    :param auth: a tuple with (username, password) for acessing CouchDB.
+    :type auth: tuple(str, str)
+    :param agent: an optional agent for doing requests, used in tests.
+    :type agent: twisted.web.client.Agent
+
+    :raise MissingCouchConfigDocumentError: raised when a database is not empty
+                                            but has no config document in it.
+
+    :raise WrongCouchSchemaVersionError: raised when a config document was
+                                         found but the schema version is
+                                         different from what is expected.
+    """
+    # if there are documents, ensure that a config doc exists
+    db_url = urljoin(url, '%s/' % db)
+    config_doc_url = urljoin(db_url, CONFIG_DOC_ID)
+    res = yield treq.get(config_doc_url, auth=auth, agent=agent)
+
+    if res.code != 200 and res.code != 404:
+        raise Exception("Unexpected HTTP response code: %d" % res.code)
+
+    elif res.code == 404:
+        res = yield treq.get(urljoin(db_url, '_all_docs'), auth=auth,
+                             params={'limit': 1}, agent=agent)
+        docs = yield res.json()
+        if docs['total_rows'] != 0:
+            logger.error(
+                "Missing couch config document in database %s" % db)
+            raise MissingCouchConfigDocumentError(db)
+
+    elif res.code == 200:
+        config_doc = yield res.json()
+        if config_doc[SCHEMA_VERSION_KEY] != SCHEMA_VERSION:
+            logger.error(
+                "Unsupported database schema in database %s" % db)
+            raise WrongCouchSchemaVersionError(db)
+
+
+def _stop(failure, reactor):
+    logger.error("Failure while checking schema versions: %r - %s"
+                 % (failure, failure.message))
+    reactor.addSystemEventTrigger('after', 'shutdown', os._exit, 1)
+    reactor.stop()
+
+
+@defer.inlineCallbacks
+def check_schema_versions(couch_url, agent=None, reactor=reactor):
+    """
+    Check that all user databases use the correct couch schema.
+
+    :param couch_url: The URL for the couch database.
+    :type couch_url: str
+    :param agent: an optional agent for doing requests, used in tests.
+    :type agent: twisted.web.client.Agent
+    :param reactor: an optional reactor for stopping in case of errors, used
+                    in tests.
+    :type reactor: twisted.internet.base.ReactorBase
+    """
+    url = urlsplit(couch_url)
+    auth = (url.username, url.password) if url.username else None
+    url = "%s://%s:%d" % (url.scheme, url.hostname, url.port)
+    res = yield treq.get(urljoin(url, '_all_dbs'), auth=auth, agent=agent)
+    dbs = yield res.json()
+    deferreds = []
+    semaphore = defer.DeferredSemaphore(20)
+    logger.info('Starting schema versions check...')
+    for db in dbs:
+        if not db.startswith('user-'):
+            continue
+        d = semaphore.run(_check_db_schema_version, url, db, auth, agent=agent)
+        d.addErrback(_stop, reactor=reactor)
+        deferreds.append(d)
+    d = defer.gatherResults(deferreds, consumeErrors=True)
+    d.addCallback(lambda _: logger.info('Finished schema versions check.'))
+    yield d
diff --git a/src/leap/soledad/common/couch/state.py b/src/leap/soledad/common/couch/state.py
index 5614b32f6a7eebc9a3cc6cc8a915091738402a42..f3645ab654c89de9fd14c4e2555f340e4a7b35ab 100644
--- a/src/leap/soledad/common/couch/state.py
+++ b/src/leap/soledad/common/couch/state.py
@@ -18,117 +18,19 @@
 Server state using CouchDatabase as backend.
 """
 import re
-import os
-import treq
 
 from six.moves.urllib.parse import urljoin
-from twisted.internet import defer
-from urlparse import urlsplit
-
-from twisted.internet import reactor
 
 from leap.soledad.common.log import getLogger
 from leap.soledad.common.couch import CouchDatabase
-from leap.soledad.common.couch import CONFIG_DOC_ID
-from leap.soledad.common.couch import SCHEMA_VERSION
-from leap.soledad.common.couch import SCHEMA_VERSION_KEY
 from leap.soledad.common.command import exec_validated_cmd
 from leap.soledad.common.l2db.remote.server_state import ServerState
 from leap.soledad.common.l2db.errors import Unauthorized
-from leap.soledad.common.errors import WrongCouchSchemaVersionError
-from leap.soledad.common.errors import MissingCouchConfigDocumentError
 
 
 logger = getLogger(__name__)
 
 
-#
-# Database schema version verification
-#
-
-@defer.inlineCallbacks
-def _check_db_schema_version(url, db, auth, agent=None):
-    """
-    Check if the schema version is up to date for a given database.
-
-    :param url: the server base URL.
-    :type url: str
-    :param db: the database name.
-    :type db: str
-    :param auth: a tuple with (username, password) for acessing CouchDB.
-    :type auth: tuple(str, str)
-    :param agent: an optional agent for doing requests, used in tests.
-    :type agent: twisted.web.client.Agent
-
-    :raise MissingCouchConfigDocumentError: raised when a database is not empty
-                                            but has no config document in it.
-
-    :raise WrongCouchSchemaVersionError: raised when a config document was
-                                         found but the schema version is
-                                         different from what is expected.
-    """
-    # if there are documents, ensure that a config doc exists
-    db_url = urljoin(url, '%s/' % db)
-    config_doc_url = urljoin(db_url, CONFIG_DOC_ID)
-    res = yield treq.get(config_doc_url, auth=auth, agent=agent)
-
-    if res.code != 200 and res.code != 404:
-        raise Exception("Unexpected HTTP response code: %d" % res.code)
-
-    elif res.code == 404:
-        res = yield treq.get(urljoin(db_url, '_all_docs'), auth=auth,
-                             params={'limit': 1}, agent=agent)
-        docs = yield res.json()
-        if docs['total_rows'] != 0:
-            logger.error(
-                "Missing couch config document in database %s" % db)
-            raise MissingCouchConfigDocumentError(db)
-
-    elif res.code == 200:
-        config_doc = yield res.json()
-        if config_doc[SCHEMA_VERSION_KEY] != SCHEMA_VERSION:
-            logger.error(
-                "Unsupported database schema in database %s" % db)
-            raise WrongCouchSchemaVersionError(db)
-
-
-def _stop(failure, reactor):
-    logger.error("Failure while checking schema versions: %r - %s"
-                 % (failure, failure.message))
-    reactor.addSystemEventTrigger('after', 'shutdown', os._exit, 1)
-    reactor.stop()
-
-
-@defer.inlineCallbacks
-def check_schema_versions(couch_url, agent=None, reactor=reactor):
-    """
-    Check that all user databases use the correct couch schema.
-
-    :param couch_url: The URL for the couch database.
-    :type couch_url: str
-    :param agent: an optional agent for doing requests, used in tests.
-    :type agent: twisted.web.client.Agent
-    :param reactor: an optional reactor for stopping in case of errors, used
-                    in tests.
-    :type reactor: twisted.internet.base.ReactorBase
-    """
-    url = urlsplit(couch_url)
-    auth = (url.username, url.password) if url.username else None
-    url = "%s://%s:%d" % (url.scheme, url.hostname, url.port)
-    res = yield treq.get(urljoin(url, '_all_dbs'), auth=auth, agent=agent)
-    dbs = yield res.json()
-    deferreds = []
-    semaphore = defer.DeferredSemaphore(20)
-    for db in dbs:
-        if not db.startswith('user-'):
-            continue
-        d = semaphore.run(_check_db_schema_version, url, db, auth, agent=agent)
-        d.addErrback(_stop, reactor=reactor)
-        deferreds.append(d)
-    d = defer.gatherResults(deferreds, consumeErrors=True)
-    yield d
-
-
 #
 # CouchDB Server state
 #
diff --git a/src/leap/soledad/server/entrypoints.py b/src/leap/soledad/server/entrypoints.py
index fa8c3ff2a18a5ddfeaece05c527c8f9876e2e3e7..0237978e12a02fcb4d24ab1ca5a7341d0f305196 100644
--- a/src/leap/soledad/server/entrypoints.py
+++ b/src/leap/soledad/server/entrypoints.py
@@ -17,20 +17,14 @@
 """
 Entrypoints for the Soledad server.
 """
-import os
-
 from twisted.internet import reactor
-from twisted.python import threadpool
 from twisted.logger import Logger
+from twisted.python import threadpool
 
-from ..common.couch.state import check_schema_versions
 from .auth import localPortal, publicPortal
 from .session import SoledadSession
-from ._config import get_config
-from ._wsgi import init_couch_state
 
 
-conf = get_config()
 log = Logger()
 
 
@@ -49,25 +43,3 @@ class ServicesEntrypoint(SoledadSession):
     def __init__(self):
         portal = localPortal()
         SoledadSession.__init__(self, portal)
-
-
-def check_conf():
-    path = conf['blobs_path']
-    blobs_not_empty = bool(os.path.exists(path) and os.listdir(path))
-    if not conf['blobs'] and blobs_not_empty:
-        message = """
-**  WARNING: Blobs is disabled, but blobs directory isn't empty.          **
-**  If it was previously enabled, disabling can cause data loss due blobs **
-**  documents not being accessible to users.                              **
-**  Blobs directory: %s
-**  REFUSING TO START. Please double check your configuration.            **
-    """
-        log.error(message % path)
-        reactor.stop()
-
-
-reactor.callWhenRunning(check_conf)
-reactor.callWhenRunning(check_schema_versions, conf['couch_url'])
-# see the comments in _wsgi.py regarding why couch state has to be
-# initialized when the reactor is running
-reactor.callWhenRunning(init_couch_state, conf)
diff --git a/src/leap/soledad/server/server.tac b/src/leap/soledad/server/server.tac
index efaca790d2bea90a8cc348b4b37fef377f7f4a5d..f6f784bc69a857e75ebd1557035badebe2d0512f 100644
--- a/src/leap/soledad/server/server.tac
+++ b/src/leap/soledad/server/server.tac
@@ -1,45 +1,122 @@
+# -*- coding: utf-8 -*-
+# server.tac
+# Copyright (C) 2017 LEAP
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
 import sys
 import os
 
 from twisted.application import service, strports
+from twisted.logger import Logger
 from twisted.web import server
-from twisted.python import log
 
+from leap.soledad.common.couch.check import check_schema_versions
 from leap.soledad.server import entrypoints
+from leap.soledad.server._wsgi import init_couch_state
+from leap.soledad.server._config import get_config
 
-application = service.Application('soledad-server')
 
-# local entrypoint
-local_port = os.getenv('LOCAL_SERVICES_PORT', 2525)
-local_description = 'tcp:%s:interface=127.0.0.1' % local_port
-local_site = server.Site(entrypoints.ServicesEntrypoint())
+logger = Logger(__name__)
+
+
+#
+# necessary checks
+#
+
+def _check_env(local_port, public_port):
+    if local_port == public_port:
+        logger.error("LOCAL_SERVICES_PORT and HTTPS_PORT can't be the same!")
+        sys.exit(20)
+
+    if public_port is None and not os.getenv('DEBUG_SERVER'):
+        logger.error("HTTPS_PORT env var is required to be set!")
+        sys.exit(20)
+
+
+def _check_conf(conf):
+    path = conf['blobs_path']
+    blobs_not_empty = bool(os.path.exists(path) and os.listdir(path))
+    if not conf['blobs'] and blobs_not_empty:
+        message = """
+**  WARNING: Blobs is disabled, but blobs directory isn't empty.          **
+**  If it was previously enabled, disabling can cause data loss due blobs **
+**  documents not being accessible to users.                              **
+**  Blobs directory: %s
+**  REFUSING TO START. Please double check your configuration.            **
+    """
+        logger.error(message % path)
+        sys.exit(20)
+
+
+#
+# service creation functions
+#
+
+def _create_local_service(port, application):
+    logger.info('Starting local Services HTTP API')
+    desc = 'tcp:%s:interface=127.0.0.1' % port
+    site = server.Site(entrypoints.ServicesEntrypoint())
+    service = strports.service(desc, site)
+    service.setServiceParent(application)
 
-local_server = strports.service(local_description, local_site)
-local_server.setServiceParent(application)
 
-# public entrypoint
-port = os.getenv('HTTPS_PORT', None)
-if port == local_port:
-    log.err("LOCAL_SERVICES_PORT and HTTPS_PORT can't be the same!")
-    sys.exit(20)
-if port:
+def _get_tls_service_description(port):
     privateKey = os.getenv('PRIVKEY_PATH', '/etc/soledad/soledad-server.key')
     certKey = os.getenv('CERT_PATH', '/etc/soledad/soledad-server.pem')
     sslmethod = os.getenv('SSL_METHOD', 'SSLv23_METHOD')
-
-    public_description = ':'.join([
+    desc = ':'.join([
         'ssl',
         'port=' + str(port),
         'privateKey=' + privateKey,
         'certKey=' + certKey,
         'sslmethod=' + sslmethod])
-elif os.getenv('DEBUG_SERVER', False):
-    public_description = 'tcp:port=2424:interface=0.0.0.0'
-else:
-    log.err("HTTPS_PORT env var is required to be set!")
-    sys.exit(20)
+    return desc
+
+
+def _create_public_service(port, application):
+    logger.info('Starting public Users HTTP API')
+    if port:
+        desc = _get_tls_service_description(port)
+    else:
+        logger.warn('Using plain HTTP on public Users API.')
+        desc = 'tcp:port=2424:interface=0.0.0.0'
+
+    site = server.Site(entrypoints.UsersEntrypoint())
+    service = strports.service(desc, site)
+    service.setServiceParent(application)
+
 
-public_site = server.Site(entrypoints.UsersEntrypoint())
+def _create_services(local_port, public_port, application):
+    _create_local_service(local_port, application)
+    _create_public_service(public_port, application)
 
-public_server = strports.service(public_description, public_site)
-public_server.setServiceParent(application)
+
+#
+# the application
+#
+
+def _run(application):
+    local_port = os.getenv('LOCAL_SERVICES_PORT', 2525)
+    public_port = os.getenv('HTTPS_PORT', None)
+    conf = get_config()
+    _check_env(local_port, public_port)
+    _check_conf(conf)
+    d = check_schema_versions(conf['couch_url'])
+    d.addCallback(lambda _: init_couch_state(conf))
+    d.addCallback(lambda _: _create_services(local_port, public_port,
+                                             application))
+
+
+application = service.Application('soledad-server')
+_run(application)
diff --git a/tests/couch/test_state.py b/tests/couch/test_state.py
index 27db5abe84ca68a08c6cec137edca69f73a6601f..d978fe56a6f6476f0a888f71b487e5a6a5a3cef7 100644
--- a/tests/couch/test_state.py
+++ b/tests/couch/test_state.py
@@ -4,8 +4,8 @@ import pytest
 from leap.soledad.common.couch import CONFIG_DOC_ID
 from leap.soledad.common.couch import SCHEMA_VERSION
 from leap.soledad.common.couch import SCHEMA_VERSION_KEY
-from leap.soledad.common.couch.state import _check_db_schema_version
-from leap.soledad.common.couch.state import check_schema_versions
+from leap.soledad.common.couch.check import _check_db_schema_version
+from leap.soledad.common.couch.check import check_schema_versions
 from uuid import uuid4
 
 from leap.soledad.common.errors import WrongCouchSchemaVersionError