diff --git a/include/ircd/listen.h b/include/ircd/listen.h
new file mode 100644
index 000000000..991eccfbd
--- /dev/null
+++ b/include/ircd/listen.h
@@ -0,0 +1,40 @@
+/* 
+ * Copyright (C) 2016 Charybdis Development Team
+ * Copyright (C) 2016 Jason Volk <jason@zemos.net>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice is present in all copies.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+ * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "socket.h"
+
+namespace ircd {
+
+struct listener
+{
+	struct acceptor;
+
+	IRCD_EXCEPTION(ircd::error, error)
+
+  private:
+	std::unique_ptr<struct acceptor> acceptor;
+
+  public:
+	listener(const json::doc &options);
+	~listener() noexcept;
+};
+
+} // namespace ircd
diff --git a/ircd/Makefile.am b/ircd/Makefile.am
index 5c4436800..c7bcec150 100644
--- a/ircd/Makefile.am
+++ b/ircd/Makefile.am
@@ -43,9 +43,10 @@ libircd_la_SOURCES =   \
 	locale.cc          \
 	http.cc            \
 	json.cc            \
-	matrix.cc          \
 	parse.cc           \
-	conf.cc
+	conf.cc            \
+	listen.cc          \
+	matrix.cc
 
 if JS
 libircd_la_SOURCES +=  \
diff --git a/ircd/ircd.cc b/ircd/ircd.cc
index cee263bcd..3489af364 100644
--- a/ircd/ircd.cc
+++ b/ircd/ircd.cc
@@ -24,6 +24,7 @@
  */
 
 #include <ircd/socket.h>
+#include <ircd/listen.h>
 #include <ircd/ctx/continuation.h>
 
 namespace ircd
@@ -103,11 +104,47 @@ try
 	//json::test();
 	//exit(0);
 
-	module listener("listen.so");
 	module client_versions("client_versions.so");
 	module client_register("client_register.so");
 	module client_login("client_login.so");
 
+	listener matrics
+	{
+		std::string { json::obj
+		{
+			{ "name",    "Chat Matrix" },
+			{ "host",    "127.0.0.1"   },
+			{ "port",     6667         },
+			{
+				"ssl",
+				{
+					{
+						"certificate",
+						{
+							{
+								"file",
+								{
+									{ "pem", "/home/jason/cdc.z.cert" }
+								}
+							}
+						}
+					},
+					{
+						"private_key",
+						{
+							{
+								"file",
+								{
+									{ "pem", "/home/jason/cdc.z.key" }
+								}
+							}
+						}
+					}
+				}
+			}
+		}}
+	};
+
 	// This is the main program loop. Right now all it does is sleep until notified
 	// to shutdown, but it can do other things eventually. Other subsystems may have
 	// spawned their own main loops.
diff --git a/ircd/listen.cc b/ircd/listen.cc
new file mode 100644
index 000000000..c8b0e0d56
--- /dev/null
+++ b/ircd/listen.cc
@@ -0,0 +1,211 @@
+/* 
+ * Copyright (C) 2016 Charybdis Development Team
+ * Copyright (C) 2016 Jason Volk <jason@zemos.net>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice is present in all copies.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+ * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <ircd/listen.h>
+
+namespace ircd {
+
+const size_t DEFAULT_STACK_SIZE
+{
+	256_KiB  // can be optimized
+};
+
+struct listener::acceptor
+{
+	static log::log log;
+
+	std::string name;
+	size_t backlog;
+
+	asio::ssl::context ssl;
+	ip::tcp::endpoint ep;
+	ip::tcp::acceptor a;
+	ctx::context context;
+
+	explicit operator std::string() const;
+
+	bool accept();
+	void main();
+
+	acceptor(const json::doc &opts);
+	~acceptor() noexcept;
+};
+
+} // namespace ircd
+
+///////////////////////////////////////////////////////////////////////////////
+//
+// ircd::listener
+//
+
+ircd::listener::listener(const json::doc &opts)
+:acceptor{std::make_unique<struct acceptor>(opts)}
+{
+}
+
+ircd::listener::~listener()
+noexcept
+{
+}
+
+///////////////////////////////////////////////////////////////////////////////
+//
+// ircd::listener::acceptor
+//
+
+ircd::log::log
+ircd::listener::acceptor::log
+{
+	"listener"
+};
+
+ircd::listener::acceptor::acceptor(const json::doc &opts)
+try
+:name
+{
+	unquote(opts.get("name", "IRCd (ssl)"s))
+}
+,backlog
+{
+	opts.get<size_t>("backlog", asio::socket_base::max_connections)
+}
+,ssl
+{
+	asio::ssl::context::method::sslv23_server
+}
+,ep
+{
+	ip::address::from_string(unquote(opts.get("host", "127.0.0.1"s))),
+	opts.get<uint16_t>("port", 6667)
+}
+,a
+{
+	*ircd::ios
+}
+,context
+{
+	"listener",
+	opts.get<size_t>("stack_size", DEFAULT_STACK_SIZE),
+	std::bind(&acceptor::main, this),
+	context.POST // defer until ctor body binds the socket
+}
+{
+	log.debug("%s attempting to open listening socket", std::string(*this));
+
+	a.open(ep.protocol());
+	a.set_option(ip::tcp::acceptor::reuse_address(true));
+	a.bind(ep);
+
+	if(opts.has("ssl.certificate.file.pem"))
+	{
+		const std::string filename{opts["ssl.certificate.file.pem"]};
+		ssl.use_certificate_file(filename, asio::ssl::context::pem);
+		log.info("%s using certificate file '%s'",
+		          std::string(*this),
+		          filename);
+	}
+
+	if(opts.has("ssl.private_key.file.pem"))
+	{
+		const std::string filename{opts["ssl.private_key.file.pem"]};
+		ssl.use_private_key_file(filename, asio::ssl::context::pem);
+		log.info("%s using private key file '%s'",
+		          std::string(*this),
+		          filename);
+	}
+
+	a.listen(backlog);
+
+	// Allows main() to run and print its log message
+	ctx::yield();
+}
+catch(const boost::system::system_error &e)
+{
+	throw error("listener: %s", e.what());
+}
+
+ircd::listener::acceptor::~acceptor()
+noexcept
+{
+	a.cancel();
+}
+
+void
+ircd::listener::acceptor::main()
+try
+{
+	log.info("%s ready", std::string(*this));
+
+	while(accept());
+
+	log.info("%s closing", std::string(*this));
+}
+catch(const ircd::ctx::interrupted &e)
+{
+	log.warning("%s interrupted", std::string(*this));
+}
+catch(const std::exception &e)
+{
+	log.error("%s %s", std::string(*this), e);
+}
+
+bool
+ircd::listener::acceptor::accept()
+try
+{
+	auto sock(std::make_shared<ircd::socket>(ssl));
+	a.async_accept(sock->ssl.lowest_layer(), yield(continuation()));
+	sock->ssl.async_handshake(socket::handshake_type::server, yield(continuation()));
+	add_client(std::move(sock));
+	return true;
+}
+catch(const boost::system::system_error &e)
+{
+	switch(e.code().value())
+	{
+		using namespace boost::system::errc;
+
+		case success:               return true;
+		case operation_canceled:    return false;
+		default:                    throw;
+	}
+}
+catch(const std::exception &e)
+{
+	log.error("%s: in accept(): %s", std::string(*this), e);
+	return true;
+}
+
+ircd::listener::acceptor::operator std::string()
+const
+{
+	std::string ret(256, char());
+	const auto length
+	{
+		fmt::snprintf(&ret.front(), ret.size(), "'%s' @ [%s]:%u",
+		              name,
+		              string(ep.address()),
+		              ep.port())
+	};
+
+	ret.resize(length);
+	return ret;
+}
diff --git a/modules/Makefile.am b/modules/Makefile.am
index 44dbd4be6..cb3a8ae53 100644
--- a/modules/Makefile.am
+++ b/modules/Makefile.am
@@ -34,6 +34,4 @@ client_module_LTLIBRARIES =     \
 	client/client_login.la
 
 moduledir=@moduledir@
-listen_la_SOURCES = listen.cc
 module_LTLIBRARIES = \
-	listen.la
diff --git a/modules/listen.cc b/modules/listen.cc
deleted file mode 100644
index b5d1ebe26..000000000
--- a/modules/listen.cc
+++ /dev/null
@@ -1,188 +0,0 @@
-/* 
- * Copyright (C) 2016 Charybdis Development Team
- * Copyright (C) 2016 Jason Volk <jason@zemos.net>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice is present in all copies.
- *
- * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
- * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
- * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
- * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
- * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
- * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
- * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
- * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
- * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
- */
-
-#include <boost/asio.hpp>
-#include <ircd/ctx/continuation.h>
-#include <ircd/socket.h>
-
-using namespace ircd;
-
-const size_t STACK_SIZE
-{
-	256_KiB  // can be optimized
-};
-
-struct listener
-{
-	static struct log::log log;
-
-	std::string name;
-	size_t backlog;
-	ip::address host;
-	ip::tcp::endpoint ep;
-	ctx::dock cond;
-	ircd::context context;
-	asio::ssl::context ssl;
-	ip::tcp::acceptor acceptor;
-
-	bool configured() const;
-
-  private:
-	bool accept();
-	void main();
-
-  public:
-	listener(const std::string &name = {});
-	listener(listener &&) = default;
-};
-
-struct log::log listener::log
-{
-	"listener", 'P'
-};
-
-listener::listener(const std::string &name)
-try
-:name
-{
-	name
-}
-,backlog
-{
-	boost::asio::socket_base::max_connections
-}
-,context
-{
-	"listener", STACK_SIZE, std::bind(&listener::main, this)
-}
-,ssl
-{
-	asio::ssl::context::method::sslv23_server
-}
-,acceptor
-{
-	*ios
-}
-{
-	ssl.use_certificate_file("/home/jason/cdc.z.cert", asio::ssl::context::pem);
-	ssl.use_private_key_file("/home/jason/cdc.z.key", asio::ssl::context::pem);
-}
-catch(const boost::system::system_error &e)
-{
-	throw error("listener: %s", e.what());
-}
-
-bool
-listener::configured()
-const
-{
-	return ep != ip::tcp::endpoint{};
-}
-
-void
-listener::main()
-try
-{
-	// The listener context starts after there is a valid configuration
-	cond.wait([this]
-	{
-		return configured();
-	});
-
-	log.debug("Attempting bind() to [%s]:%u",
-	                ep.address().to_string().c_str(),
-	                ep.port());
-
-	acceptor.open(ep.protocol());
-	acceptor.set_option(ip::tcp::acceptor::reuse_address(true));
-	acceptor.bind(ep);
-	acceptor.listen(backlog);
-	log.info("Listener bound to [%s]:%u",
-	         ep.address().to_string().c_str(),
-	         ep.port());
-
-	while(accept());
-
-	log.info("Listener closing @ [%s]:%u",
-	          ep.address().to_string().c_str(),
-	          ep.port());
-}
-catch(const ircd::ctx::interrupted &e)
-{
-	log.warning("Listener closing @ [%s]:%u: %s",
-	            ep.address().to_string().c_str(),
-	            ep.port(),
-	            e.what());
-}
-catch(const std::exception &e)
-{
-	log.error("Listener closing @ [%s]:%u: %s",
-	          ep.address().to_string().c_str(),
-	          ep.port(),
-	          e.what());
-}
-
-bool
-listener::accept()
-try
-{
-	auto sock(std::make_shared<ircd::socket>(ssl));
-	acceptor.async_accept(sock->ssl.lowest_layer(), yield(continuation()));
-	sock->ssl.async_handshake(socket::handshake_type::server, yield(continuation()));
-	add_client(std::move(sock));
-	return true;
-}
-catch(const boost::system::system_error &e)
-{
-	switch(e.code().value())
-	{
-		using namespace boost::system::errc;
-
-		case success:               return true;
-		case operation_canceled:    return false;
-		default:                    throw;
-	}
-}
-catch(const std::exception &e)
-{
-	log.error("Listener @ [%s]:%u: accept(): %s",
-	          ep.address().to_string().c_str(),
-	          ep.port(),
-	          e.what());
-
-	return true;
-}
-
-std::map<std::string, listener> listeners;
-
-extern "C" void gogo()
-{
-	const auto iit(listeners.emplace("foo"s, "foo"s));
-	auto &foo(iit.first->second);
-	foo.host = ip::address::from_string("127.0.0.1");
-	foo.ep = ip::tcp::endpoint(foo.host, 6667);
-	foo.cond.notify_one();
-}
-
-mapi::header IRCD_MODULE
-{
-	"P-Line - instructions for listening sockets", &gogo
-};