diff --git a/bin/browserid b/bin/browserid
index 2eb922c05a66d4c8f321f7afc2e7c890261d15c9..9671ad3ee3632bc3dc0beb6e0e80dde64826261d 100755
--- a/bin/browserid
+++ b/bin/browserid
@@ -68,6 +68,12 @@ if (!config.get('keysigner_url')) {
   process.exit(1);
 }
 
+// verify that we have a dbwriter configured
+if (!config.get('dbwriter_url')) {
+  logger.error('missing required configuration - url for the dbwriter (DBWRITER_URL in env)');
+  process.exit(1);
+}
+
 // NOTE: ordering of middleware registration is important in this file, it is the
 // order in which middleware will be invoked as requests are processed.
 
@@ -75,7 +81,10 @@ if (!config.get('keysigner_url')) {
 // This is in front of logging on purpose.  see issue #537
 heartbeat.setup(app, function(cb) {
   // let's check stuff!  first the heartbeat of our keysigner
-  heartbeat.check(config.get('keysigner_url'), cb);
+  heartbeat.check(config.get('keysigner_url'), function(rv) {
+    if (!rv) return cb(rv);
+    heartbeat.check(config.get('dbwriter_url'), cb);
+  });
 });
 
 // #2 - logging!  all requests other than __heartbeat__ are logged
@@ -141,7 +150,9 @@ app.use(function(req, resp, next) {
 config.performSubstitution(app);
 
 // #8 - handle /wsapi requests
-wsapi.setup(app);
+wsapi.setup({
+  forward_writes: config.get('dbwriter_url')
+}, app);
 
 // #9 - handle views for dynamicish content
 views.setup(app);
diff --git a/bin/dbwriter b/bin/dbwriter
index 79bfbf63baf3364de64126f76cce30b8ba1000fd..91296cae549aecc1fb51288e3a7591af9887cae6 100755
--- a/bin/dbwriter
+++ b/bin/dbwriter
@@ -1,3 +1,138 @@
 #!/usr/bin/env node
 
-require('./browserid');
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla BrowserID.
+ *
+ * The Initial Developer of the Original Code is Mozilla.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+const
+fs = require('fs'),
+path = require('path'),
+url = require('url'),
+http = require('http');
+urlparse = require('urlparse'),
+express = require('express');
+
+const
+wsapi = require('../lib/wsapi.js'),
+httputils = require('../lib/httputils.js'),
+secrets = require('../lib/secrets.js'),
+db = require('../lib/db.js'),
+config = require('../lib/configuration.js'),
+heartbeat = require('../lib/heartbeat.js'),
+metrics = require('../lib/metrics.js'),
+logger = require('../lib/logging.js').logger,
+forward = require('../lib/browserid/http_forward'),
+shutdown = require('../lib/shutdown'),
+views = require('../lib/browserid/views.js');
+
+var app = undefined;
+
+app = express.createServer();
+
+logger.info("dbwriter starting up");
+
+// Setup health check / heartbeat middleware.
+// This is in front of logging on purpose.  see issue #537
+heartbeat.setup(app);
+
+// logging!  all requests other than __heartbeat__ are logged
+app.use(express.logger({
+  format: config.get('express_log_format'),
+  stream: {
+    write: function(x) {
+      logger.info(typeof x === 'string' ? x.trim() : x);
+    }
+  }
+}));
+
+// Add Strict-Transport-Security headers if we're serving over SSL
+if (config.get('scheme') == 'https') {
+  app.use(function(req, resp, next) {
+    // expires in 30 days, include subdomains like www
+    resp.setHeader("Strict-Transport-Security", "max-age=2592000; includeSubdomains");
+    next();
+    });
+}
+
+// prevent framing of everything.  content underneath that needs to be
+// framed must explicitly remove the x-frame-options
+app.use(function(req, resp, next) {
+  resp.setHeader('x-frame-options', 'DENY');
+  next();
+});
+
+// verify all JSON responses are objects - prevents regression on issue #217
+app.use(function(req, resp, next) {
+  var realRespJSON = resp.json;
+  resp.json = function(obj) {
+    if (!obj || typeof obj !== 'object') {
+      logger.error("INTERNAL ERROR!  *all* json responses must be objects");
+      throw "internal error";
+    }
+    realRespJSON.call(resp, obj);
+  };
+  return next();
+});
+
+// handle /wsapi requests
+wsapi.setup({
+  only_write_apis: true
+}, app);
+
+// calls to /code_update from localhost will restart the daemon,
+// this feature is not externally accessible and is only used by
+// the update logic
+shutdown.installUpdateHandler(app, function(readyForShutdown) {
+  logger.debug("closing database connection");
+  db.close(readyForShutdown)
+});
+
+// open the databse
+db.open(config.get('database'), function (error) {
+  if (error) {
+    logger.error("can't open database: " + error);
+    // let async logging flush, then exit 1
+    return setTimeout(function() { process.exit(1); }, 0);
+  }
+
+  // shut down express gracefully on SIGINT
+  shutdown.handleTerminationSignals(app, function(readyForShutdown) {
+    db.close(readyForShutdown)
+  });
+
+  var bindTo = config.get('bind_to');
+  app.listen(bindTo.port, bindTo.host, function() {
+    logger.info("running on http://" + app.address().address + ":" + app.address().port);
+  });
+});
diff --git a/lib/configuration.js b/lib/configuration.js
index 87230eea6f685b14f4eb799c75f9cff2927afacb..5151ffba82ff6125a9f0a01d9314ad7da94fabeb 100644
--- a/lib/configuration.js
+++ b/lib/configuration.js
@@ -158,6 +158,12 @@ if (process.env['KEYSIGNER_URL']) {
   g_config.keysigner_url = url;
 }
 
+if (process.env['DBWRITER_URL']) {
+  var url = urlparse(process.env['DBWRITER_URL']).validate().normalize();
+  if (!url.port) url.port = (url.scheme === 'http') ? 80 : 443;
+  g_config.dbwriter_url = url;
+}
+
 // extract smtp params from the environment
 if (!g_config.smtp) {
   g_config.smtp = {
diff --git a/lib/wsapi.js b/lib/wsapi.js
index 3c14d7d53e053d385f858ddf3bdd2924d9f5838c..b768db346b82e57b2862d7e0a936b7f2e3947caa 100644
--- a/lib/wsapi.js
+++ b/lib/wsapi.js
@@ -32,7 +32,6 @@ function clearAuthenticatedUser(session) {
   });
 }
 
-
 function isAuthed(req) {
   var who;
   try {
@@ -84,7 +83,7 @@ exports.isAuthed = isAuthed;
 exports.bcryptPassword = bcryptPassword;
 exports.setAuthenticatedUser = setAuthenticatedUser;
 
-exports.setup = function(app) {
+exports.setup = function(options, app) {
 
   // XXX: we can and should make all of the logic below only take effect for POST requests
   // to /wsapi to reduce code run for other requests (cookie parsing, etc)
@@ -182,6 +181,12 @@ exports.setup = function(app) {
 
     try {
       var api = require(path.join(__dirname, 'wsapi', f));
+
+      // don't register read apis if we are configured as a writer
+      if (options.only_write_apis && !api.writes_db) return;
+
+      // XXX forward writes if options.forward_writes is defined
+
       wsapis[operation] = api;
 
       // set up the argument validator
diff --git a/scripts/run_locally.js b/scripts/run_locally.js
index 6c48078ec856a57b424559dc710ef94bbeb0930a..9127578f76e8214fdeccec8917abea8970b0a596 100755
--- a/scripts/run_locally.js
+++ b/scripts/run_locally.js
@@ -17,6 +17,10 @@ var daemonsToRun = {
     PORT: 10003,
     HOST: HOST
   },
+  dbwriter: {
+    PORT: 10004,
+    HOST: HOST
+  },
   example: {
     path: path.join(__dirname, "..", "scripts", "serve_example.js"),
     PORT: 10001,
@@ -32,6 +36,7 @@ var daemonsToRun = {
 process.env['LOG_TO_CONSOLE'] = 1;
 
 // all spawned processes will communicate with the local browserid
+process.env['DBWRITER_URL'] = 'http://' + HOST + ":10004";
 process.env['BROWSERID_URL'] = 'http://' + HOST + ":10002";
 process.env['VERIFIER_URL'] = 'http://' + HOST + ":10000/verify";
 process.env['KEYSIGNER_URL'] = 'http://' + HOST + ":10003";