diff --git a/bin/browserid b/bin/browserid
index 70fdce33c78a2625fa69887fd1ad0828cd27f225..936647854c8c1908e335b8bbf923a6552a42bfdf 100755
--- a/bin/browserid
+++ b/bin/browserid
@@ -101,10 +101,11 @@ app.use(function(req, resp, next) {
 // #5 - redirection!  redirect requests to the "verifier" or to the "dbwriter"
 // processes
 if (config.get('verifier_url')) {
+  var verifier_url = urlparse(config.get('verifier_url')).validate().normalize();
   app.use(function(req, res, next) {
     if (/^\/verify$/.test(req.url)) {
       forward(
-        config.get('verifier_url'), req, res,
+        verifier_url, req, res,
         function(err) {
           if (err) {
             logger.error("error forwarding request:", err);
@@ -136,7 +137,7 @@ config.performSubstitution(app);
 
 // #8 - handle /wsapi requests
 wsapi.setup({
-  forward_writes: config.get('dbwriter_url')
+  forward_writes: urlparse(config.get('dbwriter_url')).validate().normalize().originOnly()
 }, app);
 
 // #9 - handle views for dynamicish content
diff --git a/bin/dbwriter b/bin/dbwriter
index 4d92017b0130935d251243fab026fb72e3d30022..456a78837d9406c83193fdf54fa2a4b9c8b42e6b 100755
--- a/bin/dbwriter
+++ b/bin/dbwriter
@@ -10,13 +10,11 @@ path = require('path'),
 url = require('url'),
 http = require('http');
 urlparse = require('urlparse'),
-express = require('express');
-
-const
+express = require('express'),
 wsapi = require('../lib/wsapi.js'),
-httputils = require('../lib/httputils.js'),
+httputils = require('../lib/httputils.js');
 secrets = require('../lib/secrets.js'),
-db = require('../lib/db.js'),
+db = require('../lib/db.js');
 config = require('../lib/configuration.js'),
 heartbeat = require('../lib/heartbeat.js'),
 metrics = require('../lib/metrics.js'),
@@ -108,7 +106,7 @@ 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);
+    return process.nextTick(function() { process.exit(1); });
   }
 
   // shut down express gracefully on SIGINT
diff --git a/bin/keysigner b/bin/keysigner
index b0478d04089c93ed4441e2955a37b039ae827f8e..b2002a722fdb8049d6137d7f3f3854e395a21a1b 100755
--- a/bin/keysigner
+++ b/bin/keysigner
@@ -19,7 +19,11 @@ metrics = require('../lib/metrics.js'),
 logger = require('../lib/logging.js').logger,
 heartbeat = require('../lib/heartbeat'),
 shutdown = require('../lib/shutdown'),
-computecluster = require('compute-cluster');
+computecluster = require('compute-cluster'),
+urlparse = require('urlparse');
+
+const HOSTNAME = urlparse(config.get('public_url')).host;
+logger.info("Certs will be issued from: " + HOSTNAME);
 
 // create an express server
 var app = express.createServer();
@@ -71,12 +75,15 @@ try {
   process.exit(1);
 }
 
+
 // and our single function
 app.post('/wsapi/cert_key', validate(["email", "pubkey"]), function(req, resp) {
   var startTime = new Date();
   cc.enqueue({
     pubkey: req.body.pubkey,
-    email: req.body.email
+    email: req.body.email,
+    validityPeriod: config.get('certificate_validity_ms'),
+    hostname: HOSTNAME
   }, function (err, r) {
     var reqTime = new Date - startTime;
     statsd.timing('certification_time', reqTime);
diff --git a/config/local.json b/config/local.json
new file mode 100644
index 0000000000000000000000000000000000000000..82aefccb452a01096ceae22aae71b26b7626deff
--- /dev/null
+++ b/config/local.json
@@ -0,0 +1,18 @@
+{
+  "verifier": { "bind_to": { "port": 10000 } }, 
+  "keysigner": { "bind_to": { "port": 10003 } }, 
+  "dbwriter": { "bind_to": { "port": 10004 } }, 
+  "proxy": { "bind_to": { "port": 10006 } }, 
+  "browserid": {
+    "bind_to": {
+      "port": 10002
+    }
+  },
+  "use_minified_resources": false,
+  "database": {
+    "driver": "json"
+  },
+  "express_log_format": "dev",
+  "email_to_console": true
+}
+
diff --git a/lib/configuration.js b/lib/configuration.js
index e5c910b0e1687e5463b1edfc2e08720471f65c73..d1fb9248d801ba3ec7091a13a496888daffd575e 100644
--- a/lib/configuration.js
+++ b/lib/configuration.js
@@ -18,7 +18,9 @@ urlparse = require('urlparse'),
 secrets = require('./secrets'),
 temp = require('temp'),
 semver = require('semver'),
-fs = require('fs');
+fs = require('fs'),
+convict = require('convict'),
+cjson = require('cjson');
 
 // verify the proper version of node.js is in use
 try {
@@ -33,205 +35,182 @@ try {
   process.exit(1);
 }
 
-var g_config = {
-};
-
-// get the value for a given key, this mechanism allows the rest of the
-// application to reach in and set
-exports.get = function(key) {
-  if (key === 'env') return process.env['NODE_ENV'];
-  return g_config[key];
-}
-
-// allow the application to set configuration variables
-// iff we're in a test_ environment
-exports.set = function(key, val) {
-  if (!/^test_/.test(exports.get('env')))
-    throw "you may only set configuration variables in a test_ environment " +
-          "(not in '" + exports.get('env') + "')";
-  g_config[key] = val;
-}
-
-// *** the various deployment configurations ***
-const g_configs = { };
-
-// production is the configuration that runs on our
-// public service (browserid.org)
-g_configs.production = {
-  URL: 'https://browserid.org',
-  use_minified_resources: true,
-  var_path: '/home/browserid/var/',
+var conf = module.exports = convict({
+  env: {
+    // XXX: should we deprecate this configuration paramater?
+    doc: "What environment are we running in?  Note: all hosted environments are 'production'.  ",
+    format: 'string ["production", "local", "test_mysql", "test_json"] = "production"',
+    env: 'NODE_ENV'
+  },
+  bind_to: {
+    host: {
+      doc: "The ip address the server should bind",
+      format: 'string = "127.0.0.1"',
+      env: 'IP_ADDRESS'
+    },
+    port: {
+      doc: "The port the server should bind",
+      format: 'integer{1,65535}?',
+      env: 'PORT'
+    }
+  },
+  public_url: {
+    doc: "The publically visible URL of the deployment",
+    format: 'string = "https://browserid.org"',
+    env: 'URL'
+  },
+  scheme: {
+    // XXX should we deprecate scheme as it's redundant and derived from 'public_url' ?
+    doc: "The scheme of the public URL.  Calculated from the latter.",
+    format: "string",
+  },
+  use_minified_resources: {
+    doc: "Should the server serve minified resources?",
+    format: 'boolean = true',
+    env: 'MINIFIED'
+  },
+  var_path: {
+    doc: "The path where deployment specific resources will be sought (keys, etc), and logs will be kept.",
+    format: 'string?',
+    env: 'VAR_PATH'
+  },
   database: {
-    driver: "mysql",
-    user: 'browserid',
-    create_schema: true,
-    may_write: false
+    driver: 'string ["json", "mysql"] = "json"',
+    user: 'string?',
+    create_schema: 'boolean = true',
+    may_write: 'boolean = true',
+    name: {
+      format: 'string?',
+      env: 'DATABASE_NAME'
+    }
+  },
+  smtp: {
+    host: 'string?',
+    user: 'string?',
+    pass: 'string?'
   },
   statsd: {
-    enabled: false
+    enabled: {
+      doc: "enable UDP based statsd reporting",
+      format: 'boolean = true',
+      env: 'ENABLE_STATSD'
+    },
+    host: "string?",
+    port: "integer{1,65535}?"
   },
-  bcrypt_work_factor: 12,
-  authentication_duration_ms: (2 * 7 * 24 * 60 * 60 * 1000),
-  certificate_validity_ms: (24 * 60 * 60 * 1000),
-  min_time_between_emails_ms: (60 * 1000),
-  // may be specified to manipulate the maximum number of compute processes
-  max_compute_processes: undefined,
-  // return a 503 if a compute process would take over 10s to complete
-  max_compute_duration: 10,
-  disable_primary_support: false,
-  // code_version is a property in the session_context response...  When
-  // enabled it causes frontend code to employ cache busting logic.  issue #226
-  enable_code_version: false,
-  /*
-  , http_proxy: {
-    port: 3128,
-    host: "127.0.0.1"
-  }
-  */
-  default_lang: ['en-US'],
-  supported_languages: ['en-US'],
-  // Contains po files
-  locale_directory: 'locale'
-};
-
-// local development configuration
-g_configs.local =  {
-  URL: 'http://127.0.0.1:10002',
-  email_to_console: true, // don't send email, just dump verification URLs to console.
-  use_minified_resources: false,
-  var_path: path.join(__dirname, "..", "var"),
-  database: {
-    driver: "json",
-    may_write: false
+  bcrypt_work_factor: {
+    doc: "How expensive should we make password checks (to mitigate brute force attacks) ?  Each increment is 2x the cost.",
+    format: 'integer{6,20} = 12',
+    env: 'BCRYPT_WORK_FACTOR',
   },
-  bcrypt_work_factor: g_configs.production.bcrypt_work_factor,
-  authentication_duration_ms: g_configs.production.authentication_duration_ms,
-  certificate_validity_ms: g_configs.production.certificate_validity_ms,
-  min_time_between_emails_ms: g_configs.production.min_time_between_emails_ms,
-  max_compute_processes: undefined,
-  max_compute_duration: 10,
-  disable_primary_support: false,
-  // code_version is a property in the session_context response...  When
-  // enabled it causes frontend code to employ cache busting logic.  issue #226
-  enable_code_version: false,
-/*
-  , http_proxy: {
-    port: 3128,
-    host: "127.0.0.1"
-  }
-*/
-  default_lang: g_configs.production.default_lang,
-  // it-CH and db-LB are debug
-  supported_languages: [
-    'af', 'ca', 'cs', 'db-LB', 'de', 'en-US', 'eo', 'es', 'es-MX', 'eu', 'fr', 
-    'fy', 'gd', 'gl', 'hr', 'it', 'ja', 'lij', 'lt', 'ml', 'nl', 'pl', 'rm', 
-    'ro', 'ru', 'sk', 'sl', 'son', 'sq', 'sr', 'tr', 'zh-CN', 'zh-TW',
-    'it-CH', 'db-LB' 
-  ], 
-  locale_directory: g_configs.production.locale_directory
-};
-
-// test environments are variations on local
-g_configs.test_json = JSON.parse(JSON.stringify(g_configs.local));
-g_configs.test_json.database = {
-  driver: "json"
-};
-
-g_configs.test_mysql = JSON.parse(JSON.stringify(g_configs.local));
-g_configs.test_mysql.database = {
-  driver: "mysql",
-  user: "test",
-  create_schema: true
-};
-
-if (undefined !== process.env['NODE_EXTRA_CONFIG']) {
-  eval(fs.readFileSync(process.env['NODE_EXTRA_CONFIG']) + '');
-}
-
-// default deployment is local
-if (undefined === process.env['NODE_ENV']) {
-  process.env['NODE_ENV'] = 'local';
-}
-
-g_config = g_configs[process.env['NODE_ENV']];
-
-if (g_config === undefined) throw "unknown environment: " + exports.get('env');
-
-// what url are we running under?
-{
-  var ourURL = process.env['BROWSERID_URL'] || g_config['URL'];
-  var purl = urlparse(ourURL).validate().normalize().originOnly();
-
-  g_config.URL = purl.toString();
-  g_config.hostname = purl.host;
-  g_config.scheme = purl.scheme;
-  g_config.port = purl.port || (purl.scheme == 'https' ? 443 : 80);
-}
-
-if (process.env['VERIFIER_URL']) {
-  var url = urlparse(process.env['VERIFIER_URL']).validate().normalize();
-  if (!url.port) url.port = (url.scheme === 'http') ? 80 : 443;
-  g_config.verifier_url = url;
-}
-
-if (process.env['KEYSIGNER_URL']) {
-  var url = urlparse(process.env['KEYSIGNER_URL']).validate().normalize();
-  if (!url.port) url.port = (url.scheme === 'http') ? 80 : 443;
-  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 = {
-    host: process.env['SMTP_HOST'],
-    user: process.env['SMTP_USER'],
-    pass: process.env['SMTP_PASS']
-  };
-}
+  authentication_duration_ms: {
+    doc: "How long may a user stay signed?",
+    format: 'integer = 1209600000'
+  },
+  certificate_validity_ms: {
+    doc: "For how long shall certificates issued by BrowserID be valid?",
+    format: 'integer = 86400000'
+  },
+  max_compute_processes: {
+    doc: "How many computation processes will be spun.  Default is good, based on the number of CPU cores on the machine.",
+    format: 'union { number{1, 256}; null; } = null',
+    env: 'MAX_COMPUTE_PROCESSES'
+  },
+  max_compute_duration: {
+    doc: "What is the longest (in seconds) we'll let the user wait before returning a 503?",
+    format: 'integer = 10'
+  },
+  disable_primary_support: {
+    doc: "Disables primary support when true",
+    format: 'boolean = false'
+  },
+  enable_code_version: {
+    doc: "When enabled, will cause a 'code version' to be returned to frontend code in `/wsapi/session_context` calls",
+    format: 'boolean = false'
+  },
+  min_time_between_emails_ms: {
+    doc: "What is the most frequently we'll allow emails to be sent to the same user?",
+    format: 'integer = 60000'
+  },
+  http_proxy: {
+    port: 'integer{1,65535}?',
+    host: 'string?'
+  },
+  default_lang: 'array { string }* = [ "en-US" ]',
+  supported_languages: 'array { string }* = [ "en-US" ]',
+  locale_directory: 'string = "locale"',
+  express_log_format: 'string [ "default", "dev" ] = "default"',
+  keysigner_url: {
+    format: 'string?',
+    env: 'KEYSIGNER_URL'
+  },
+  verifier_url: {
+    format: 'string?',
+    env: 'VERIFIER_URL'
+  },
+  dbwriter_url: {
+    format: 'string?',
+    env: 'DBWRITER_URL'
+  },
+  process_type: 'string',
+  email_to_console: 'boolean = false'
+});
 
-// now handle ephemeral database configuration.  Used in testing.
-if (g_config.database.driver === 'mysql') {
-  if (process.env['MYSQL_DATABASE_NAME']) {
-    g_config.database.database = process.env['MYSQL_DATABASE_NAME'];
-  }
-} else if (g_config.database.driver === 'json') {
-  if (process.env['JSON_DATABASE_PATH']) {
-    g_config.database.path = process.env['JSON_DATABASE_PATH'];
-  }
+// At the time this file is required, we'll determine the "process name" for this proc
+// if we can determine what type of process it is (browserid or verifier) based
+// on the path, we'll use that, otherwise we'll name it 'ephemeral'.
+conf.set('process_type', path.basename(process.argv[1], ".js"));
+
+// handle configuration files.  you can specify a CSV list of configuration
+// files to process, which will be overlayed in order, in the CONFIG_FILES
+// environment variable
+if (process.env['CONFIG_FILES']) {
+  var files = process.env['CONFIG_FILES'].split(',');
+  files.forEach(function(file) {
+    var c = cjson.load(file);
+
+    // now support process-specific "overlays".  That is,
+    // .browserid.port will override .port for the "browserid" process
+
+    // first try to extract *our* overlay
+    var overlay = c[conf.get('process_type')];
+
+    // now remove all overlays from the top level config
+    fs.readdirSync(path.join(__dirname, '..', 'bin')).forEach(function(type) {
+      delete c[type];
+    });
+
+    // load the base config and the overlay in order
+    conf.load(c);
+    if (overlay) conf.load(overlay);
+  });
 }
 
-// allow work factor to be twaddled from the environment
-if (process.env['BCRYPT_WORK_FACTOR']) {
-  g_config.bcrypt_work_factor = parseInt(process.env['BCRYPT_WORK_FACTOR']);
+// special handling of HTTP_PROXY env var
+if (process.env['HTTP_PROXY']) {
+  var p = process.env['HTTP_PROXY'].split(':');
+  conf.set('http_proxy.host', p[0]);
+  conf.set('http_proxy.port', p[1]);
 }
 
-// allow the number of cores used to be specified from the environment,
-// default will something reasonable.
-if (process.env['MAX_COMPUTE_PROCESSES']) {
-  g_config.max_compute_processes = parseInt(process.env['MAX_COMPUTE_PROCESSES']);
-}
+// set the 'scheme' of the server based on the public_url (which is needed for
+// things like
+conf.set('scheme', urlparse(conf.get('public_url')).scheme);
 
-// allow var_path to be specified in the environment
-if (process.env['VAR_PATH']) {
-  g_config.var_path = process.env['VAR_PATH'];
+// if var path has not been set, let's default to var/
+if (!conf.has('var_path')) {
+  conf.set('var_path', path.join(__dirname, "..", "var"));
 }
 
-// allow statsd to be enabled from the environment
-if (process.env['ENABLE_STATSD']) {
-  g_config.statsd = { enabled: true };
+// test environments may dictate which database to use.
+if (conf.get('env') === 'test_json') {
+  conf.set('database.driver', 'json');
+} else if (conf.get('env') === 'test_mysql') {
+  conf.set('database.driver', 'mysql');
 }
 
-// what host/port shall we bind to?
-g_config.bind_to = {
-  host: process.env['IP_ADDRESS'] || process.env['HOST'] || "127.0.0.1",
-  port: process.env['PORT'] || 0
-};
+// validate the configuration based on the above specification
+conf.validate();
 
 /*
  * Install middleware that will perform textual replacement on served output
@@ -242,27 +221,15 @@ g_config.bind_to = {
  * if the host, port, or scheme are different than https://browserid.org:443
  * (all source files always should have the production hostname written into them)
  */
-exports.performSubstitution = function(app) {
-  if (g_config['URL'] != 'https://browserid.org') {
+module.exports.performSubstitution = function(app) {
+  if (conf.get('public_url') != 'https://browserid.org') {
     app.use(postprocess.middleware(function(req, buffer) {
-      return buffer.toString().replace(new RegExp('https://browserid.org', 'g'), g_config['URL']);
+      return buffer.toString().replace(new RegExp('https://browserid.org', 'g'), conf.get('public_url'));
     }));
   }
 };
 
-g_config['express_log_format'] = (exports.get('env') === 'production' ? 'default' : 'dev');
-
-// At the time this file is required, we'll determine the "process name" for this proc
-// if we can determine what type of process it is (browserid or verifier) based
-// on the path, we'll use that, otherwise we'll name it 'ephemeral'.
-g_config['process_type'] = path.basename(process.argv[1], ".js");
-
-// only allow the dbwriter process to write to the database (or the unit tests)
-g_config.database.may_write = (g_config.process_type === 'dbwriter' ||
-                               g_config.process_type === 'vows' ||
-                               g_config.process_type === 'db-test');
-
 // log the process_type
-setTimeout(function() {
-  require("./logging.js").logger.info("process type is " + g_config["process_type"]);
-}, 0);
+process.nextTick(function() {
+  require("./logging.js").logger.info("process type is " + conf.get("process_type"));
+});
diff --git a/lib/db/mysql.js b/lib/db/mysql.js
index 93a7ce18c1b076cd06beb34e0ad03e4d5e95cebb..8b8839635b94a59297da75df9651d1457412d4f3 100644
--- a/lib/db/mysql.js
+++ b/lib/db/mysql.js
@@ -89,7 +89,7 @@ exports.open = function(cfg, cb) {
   });
 
   // let's figure out the database name
-  var database = cfg.database;
+  var database = cfg.name;
   if (!database) database = "browserid";
 
   // create the client
diff --git a/lib/email.js b/lib/email.js
index 541f1315647a2d20ed26c62c51ef3ee9cce54e43..8634eede0b965ffd995b10afc022589c12ab0f36 100644
--- a/lib/email.js
+++ b/lib/email.js
@@ -11,7 +11,7 @@ config = require('./configuration.js'),
 logger = require('./logging.js').logger;
 
 /* if smtp parameters are configured, use them */
-var smtp_params = config.get('smtp');
+try { var smtp_params = config.get('smtp'); } catch(e) {};
 if (smtp_params && smtp_params.host) {
   emailer.SMTP = { host: smtp_params.host };
   logger.info("delivering email via SMTP host: " +  emailer.SMTP.host);
@@ -46,7 +46,7 @@ exports.setInterceptor = function(callback) {
 
 //TODO send in localeContext
 function doSend(landing_page, email, site, secret, langContext) {
-  var url = config.get('URL') + "/" + landing_page + "?token=" + encodeURIComponent(secret),
+  var url = config.get('public_url') + "/" + landing_page + "?token=" + encodeURIComponent(secret),
       _ = langContext.gettext,
       format = langContext.format;
 
diff --git a/lib/keysigner/ca.js b/lib/keysigner/ca.js
index 75937a871083fcd9d8739f610b3408ab4f308c7d..bc7be67f663cd32d6f7ce11b245021477a0f73cf 100644
--- a/lib/keysigner/ca.js
+++ b/lib/keysigner/ca.js
@@ -9,13 +9,9 @@ var jwcert = require('jwcrypto/jwcert'),
     jws = require('jwcrypto/jws'),
     path = require("path"),
     fs = require("fs"),
-    config = require('../configuration.js'),
     secrets = require('../secrets.js'),
-    logger = require('../logging.js').logger;
-
-const HOSTNAME = config.get('hostname');
-logger.info("Certs will be issued from: " + HOSTNAME);
-
+    logger = require('../logging.js').logger,
+    urlparse = require('urlparse');
 
 try {
   const secret_key = secrets.loadSecretKey();
@@ -35,19 +31,19 @@ function parseCert(serializedCert) {
   return cert;
 }
 
-function certify(email, publicKey, expiration) {
+function certify(hostname, email, publicKey, expiration) {
   if (expiration == null)
     throw "expiration cannot be null";
-  return new jwcert.JWCert(HOSTNAME, expiration, new Date(), publicKey, {email: email}).sign(secret_key);
+  return new jwcert.JWCert(hostname, expiration, new Date(), publicKey, {email: email}).sign(secret_key);
 }
 
-function verifyChain(certChain, cb) {
+function verifyChain(hostname, certChain, cb) {
   // raw certs
   return jwcert.JWCert.verifyChain(
     certChain, new Date(),
     function(issuer, next) {
       // for now we only do browserid.org issued keys
-      if (issuer != HOSTNAME)
+      if (issuer != hostname)
         return next(null);
 
       next(exports.PUBLIC_KEY);
diff --git a/lib/keysigner/keysigner-compute.js b/lib/keysigner/keysigner-compute.js
index d6fdaddc713f80e31432b23dbf9be495b2d3fdbf..5a64adb0c92e6e8172a70b4ff51946097ad4a7f4 100644
--- a/lib/keysigner/keysigner-compute.js
+++ b/lib/keysigner/keysigner-compute.js
@@ -3,7 +3,6 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 const
-config = require('../configuration'),
 ca = require('./ca.js');
 
 process.on('message', function(m) {
@@ -14,8 +13,8 @@ process.on('message', function(m) {
     // same account, we certify the key
     // we certify it for a day for now
     var expiration = new Date();
-    expiration.setTime(new Date().valueOf() + config.get('certificate_validity_ms'));
-    var cert = ca.certify(m.email, pk, expiration);
+    expiration.setTime(new Date().valueOf() + m.validityPeriod);
+    var cert = ca.certify(m.hostname, m.email, pk, expiration);
     process.send({"success": cert});
   } catch(e) {
     process.send({"error": e ? e.toString() : "unknown"});
diff --git a/lib/logging.js b/lib/logging.js
index e6bd272da089ccb2ce48bb3dfcaa4f25eda3473e..5b13b397e7207cd7b28a5cc03437c56d6c34a0a7 100644
--- a/lib/logging.js
+++ b/lib/logging.js
@@ -40,7 +40,7 @@ exports.logger = new (winston.Logger)({
     timestamp: true,
     filename: filename,
     colorize: true,
-    handleExceptions: true
+    handleExceptions: !!process.env['LOG_TO_CONSOLE']
   })]
 });
 
@@ -51,6 +51,6 @@ exports.enableConsoleLogging = function() {
   });
 };
 
-winston.handleExceptions();
+
 
 if (process.env['LOG_TO_CONSOLE']) exports.enableConsoleLogging();
diff --git a/lib/primary.js b/lib/primary.js
index c0996da11b540f415c373fc61583f8041d8e8832..6a0959bba0efa6a9921776a661126030092d2b59 100644
--- a/lib/primary.js
+++ b/lib/primary.js
@@ -22,6 +22,8 @@ const WELL_KNOWN_URL = "/.well-known/browserid";
 // cache .well-known/browserid for six hours
 const MAX_CACHE_MS = (6 * 60 * 60 * 1000);
 
+const HOSTNAME = urlparse(config.get('public_url')).host;
+
 function parseWellKnownBody(body, domain) {
   var v = JSON.parse(body);
 
@@ -191,7 +193,7 @@ exports.verifyAssertion = function(assertion, cb) {
     bundle.certificates,
     new Date(), function(issuer, next) {
       // issuer cannot be the browserid
-      if (issuer === config.get('hostname')) {
+      if (issuer === HOSTNAME) {
         cb("cannot authenticate to browserid with a certificate issued by it.");
       } else {
         exports.getPublicKey(issuer, function(err, pubKey) {
@@ -205,7 +207,7 @@ exports.verifyAssertion = function(assertion, cb) {
         tok.parse(bundle.assertion);
 
         // audience must be browserid itself
-        var want = urlparse(config.get('URL')).originOnly();
+        var want = urlparse(config.get('public_url')).originOnly();
         var got = urlparse(tok.audience).originOnly();
 
         if (want.toString() !== got.toString()) {
diff --git a/lib/verifier/certassertion.js b/lib/verifier/certassertion.js
index 497de7cea7efeae05cb800f7110bc4910c68e04e..b437fe4f9abbc5ecbcb38b66ae71e21557ceda50 100644
--- a/lib/verifier/certassertion.js
+++ b/lib/verifier/certassertion.js
@@ -13,7 +13,8 @@ vep = require("jwcrypto/vep"),
 config = require("../configuration.js"),
 logger = require("../logging.js").logger,
 secrets = require('../secrets.js'),
-primary = require('../primary.js');
+primary = require('../primary.js'),
+urlparse = require('urlparse');
 
 try {
   const publicKey = secrets.loadPublicKey();
@@ -23,7 +24,9 @@ try {
   setTimeout(function() { process.exit(1); }, 0);
 }
 
-logger.debug("This verifier will accept assertions issued by " + config.get('hostname'));
+const HOSTNAME = urlparse(config.get('public_url')).host;
+
+logger.debug("This verifier will accept assertions issued by " + HOSTNAME);
 
 // compare two audiences:
 //   *want* is what was extracted from the assertion (it's trusted, we
@@ -102,10 +105,10 @@ function verify(assertion, audience, successCB, errorCB) {
       ultimateIssuer = issuer;
 
       // allow other retrievers for testing
-      if (issuer === config.get('hostname')) return next(publicKey);
+      if (issuer === HOSTNAME) return next(publicKey);
       else if (config.get('disable_primary_support')) {
         return errorCB("this verifier doesn't respect certs issued from domains other than: " +
-                       config.get('hostname'));
+                       HOSTNAME);
       }
 
       // XXX: this network work happening inside a compute process.
@@ -135,7 +138,7 @@ function verify(assertion, audience, successCB, errorCB) {
       // NOTE: for "delegation of authority" support we'll need to make this check
       // more sophisticated
       var domainFromEmail = principal.email.replace(/^.*@/, '');
-      if (ultimateIssuer != config.get('hostname') && ultimateIssuer !== domainFromEmail)
+      if (ultimateIssuer != HOSTNAME && ultimateIssuer !== domainFromEmail)
       {
         return errorCB("issuer issue '" + ultimateIssuer + "' may not speak for emails from '"
                        + domainFromEmail + "'");
diff --git a/lib/wsapi.js b/lib/wsapi.js
index 66ce3e625752223552e15d9ba5728e789e0b86ff..8b7c2881a750fa4c94462e883f3ac69a8d83052b 100644
--- a/lib/wsapi.js
+++ b/lib/wsapi.js
@@ -31,7 +31,6 @@ statsd = require('./statsd');
 bcrypt = require('./bcrypt');
 i18n = require('./i18n');
 
-
 var abide = i18n.abide({
   supported_languages: config.get('supported_languages'),
   default_lang: config.get('default_lang'),
@@ -246,7 +245,7 @@ exports.setup = function(options, app) {
           !wsapis[operation].disallow_forward)
       {
         forwardedOperations.push(operation);
-        var forward_url = options.forward_writes + "wsapi/" + operation;
+        var forward_url = options.forward_writes + "/wsapi/" + operation;
         wsapis[operation].process = function(req, res) {
           forward(forward_url, req, res, function(err) {
             if (err) {
@@ -322,14 +321,11 @@ exports.setup = function(options, app) {
       wsapis[operation].validate(req, resp, function() {
         if (wsapis[operation].i18n) {
           abide(req, resp, function () {
-              console.log('WSAPI running i18n code');
-              wsapis[operation].process(req, resp);
+            wsapis[operation].process(req, resp);
           });
         } else {
-          console.log('WSAPI SKIPPING i18n code');
           wsapis[operation].process(req, resp);
         }
-
       });
     } else {
       next();
diff --git a/lib/wsapi/cert_key.js b/lib/wsapi/cert_key.js
index 22f65a432e2e8aa66e33a006305d948082ce7c6c..84b1b7582dd865174d381eae326723a3661c410a 100644
--- a/lib/wsapi/cert_key.js
+++ b/lib/wsapi/cert_key.js
@@ -7,7 +7,8 @@ db = require('../db.js'),
 httputils = require('../httputils'),
 logger = require('../logging.js').logger,
 forward = require('../http_forward.js'),
-config = require('../configuration.js');
+config = require('../configuration.js'),
+urlparse = require('urlparse');
 
 exports.method = 'post';
 exports.writes_db = false;
@@ -21,7 +22,7 @@ exports.process = function(req, res) {
     if (!owned) return httputils.badRequest(res, "that email does not belong to you");
 
     // forward to the keysigner!
-    var keysigner = config.get('keysigner_url');
+    var keysigner = urlparse(config.get('keysigner_url'));
     keysigner.path = '/wsapi/cert_key';
     forward(keysigner, req, res, function(err) {
       if (err) {
diff --git a/lib/wsapi/stage_email.js b/lib/wsapi/stage_email.js
index e26980150a4a05b9a24cf7f77adc5f952ce714f8..c5b562304f4ca262e5eff7b2e66e025bb435b783 100644
--- a/lib/wsapi/stage_email.js
+++ b/lib/wsapi/stage_email.js
@@ -38,7 +38,6 @@ exports.process = function(req, res) {
         req.session.pendingAddition = secret;
 
         res.json({ success: true });
-
         // let's now kick out a verification email!
         email.sendAddAddressEmail(req.body.email, req.body.site, secret, langContext);
       });
diff --git a/package.json b/package.json
index a2cd5c9afcad895d5997cb7f7c328c4225b311cc..62d82da3def771bb5ac45baea2041fd2effbeef4 100644
--- a/package.json
+++ b/package.json
@@ -7,6 +7,8 @@
         "bcrypt": "0.4.1",
         "compute-cluster": "0.0.6",
         "connect": "1.7.2",
+        "convict": "0.0.6",
+        "cjson": "0.0.6",
         "client-sessions": "0.0.3",
         "connect-cookie-session": "0.0.2",
         "connect-logger-statsd": "0.0.1",
@@ -30,7 +32,7 @@
     "devDependencies": {
         "vows": "0.5.13",
         "aws-lib": "0.0.5"
-    }, 
+    },
     "scripts": {
         "postinstall": "./scripts/generate_ephemeral_keys.sh",
         "test": "./scripts/run_all_tests.sh",
diff --git a/scripts/run_locally.js b/scripts/run_locally.js
index b5670b35839e2f68aa422541b9b893a46b9ce7bc..fa12dca0bd0c5581c5cef70e6f4e49d21edcd372 100755
--- a/scripts/run_locally.js
+++ b/scripts/run_locally.js
@@ -3,10 +3,8 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-
-const
+const path = require('path'),
 spawn = require('child_process').spawn,
-path = require('path'),
 config = require('../lib/configuration.js'),
 temp = require('temp'),
 secrets = require('../lib/secrets.js');
@@ -16,18 +14,9 @@ exports.daemons = daemons = {};
 const HOST = process.env['IP_ADDRESS'] || process.env['HOST'] || "127.0.0.1";
 
 var daemonsToRun = {
-  verifier: {
-    PORT: 10000,
-    HOST: HOST
-  },
-  keysigner: {
-    PORT: 10003,
-    HOST: HOST
-  },
-  dbwriter: {
-    PORT: 10004,
-    HOST: HOST
-  },
+  verifier: { },
+  keysigner: { },
+  dbwriter: { },
   example: {
     path: path.join(__dirname, "..", "scripts", "serve_example.js"),
     PORT: 10001,
@@ -39,18 +28,17 @@ var daemonsToRun = {
     PORT: 10005,
     HOST: HOST
   },
-  proxy: {
-    PORT: 10006,
-    HOST: HOST
-  },
-  browserid: {
-    PORT: 10002,
-    HOST: HOST
-  }
+  proxy: { },
+  browserid: { }
 };
 
 // route outbound HTTP through our in-tree proxy to always test said codepath
-process.env['HTTP_PROXY'] = HOST + ":10006"; 
+process.env['HTTP_PROXY'] = HOST + ":10006";
+
+process.env['HOST'] = HOST
+
+// use the "local" configuration
+process.env['CONFIG_FILES'] = path.join(__dirname, '..', 'config', 'local.json');
 
 // all spawned process that use handle primaries should know about "shimmed"
 // primaries
@@ -66,16 +54,18 @@ process.env['BROWSERID_URL'] = 'http://' + HOST + ":10002";
 process.env['VERIFIER_URL'] = 'http://' + HOST + ":10000/verify";
 process.env['KEYSIGNER_URL'] = 'http://' + HOST + ":10003";
 
+process.env['URL'] = process.env['BROWSERID_URL'];
+
 // if the environment is a 'test_' environment, then we'll use an
 // ephemeral database
 if (config.get('env').substr(0,5) === 'test_') {
   if (config.get('database').driver === 'mysql') {
-    process.env['MYSQL_DATABASE_NAME'] =
-      process.env['MYSQL_DATABASE_NAME'] ||"browserid_tmp_" + secrets.generate(6);
-    console.log("temp mysql database:", process.env['MYSQL_DATABASE_NAME']);
+    process.env['DATABASE_NAME'] =
+      process.env['DATABASE_NAME'] || "browserid_tmp_" + secrets.generate(6);
+    console.log("temp mysql database:", process.env['DATABASE_NAME']);
   } else if (config.get('database').driver === 'json') {
-    process.env['JSON_DATABASE_PATH'] =  process.env['JSON_DATABASE_PATH'] || temp.path({suffix: '.db'});
-    console.log("temp json database:", process.env['JSON_DATABASE_PATH']);
+    process.env['DATABASE_NAME'] =  process.env['DATABASE_NAME'] || temp.path({suffix: '.db'});
+    console.log("temp json database:", process.env['DATABASE_NAME']);
   }
 }
 
diff --git a/scripts/test_db_connectivity.js b/scripts/test_db_connectivity.js
index f7c8bb71a9201d2c6736ec78ab5cc80349bd298e..3b1bad4283c7252c0c3f8b99466585097d85c43b 100755
--- a/scripts/test_db_connectivity.js
+++ b/scripts/test_db_connectivity.js
@@ -6,6 +6,11 @@
 
 // a simple script to test to see if we can connect to
 // the database using the present configuration.
+const path = require('path');
+
+if (!process.env['CONFIG_FILES']) {
+  process.env['CONFIG_FILES'] = path.join(__dirname, "..", "config", "local.json");
+}
 
 const
 configuration = require('../lib/configuration.js'),
diff --git a/tests/ca-test.js b/tests/ca-test.js
index 3f0a749b9c15281b0deebdde72b54878fb224661..ecce1e0897dc7e4c7103bde217444a26d2a4ade7 100755
--- a/tests/ca-test.js
+++ b/tests/ca-test.js
@@ -32,7 +32,7 @@ suite.addBatch({
     topic: function() {
       var expiration = new Date();
       expiration.setTime(new Date().valueOf() + 5000);
-      return ca.certify(email_addr, kp.publicKey, expiration);
+      return ca.certify('127.0.0.1', email_addr, kp.publicKey, expiration);
     },
     "parses" : function(cert_raw, err) {
       var cert = ca.parseCert(cert_raw);
@@ -42,7 +42,7 @@ suite.addBatch({
       // FIXME we might want to turn this into a true async test
       // rather than one that is assumed to be synchronous although
       // it has an async structure
-      ca.verifyChain([cert_raw], function(pk) {
+      ca.verifyChain('127.0.0.1', [cert_raw], function(pk) {
         assert.isTrue(kp.publicKey.equals(pk));
       });
     }
diff --git a/tests/cert-emails-test.js b/tests/cert-emails-test.js
index 8e1a129c50cfa495a5fd4c6b3d47a50de6460157..d58dea80a63c7fe6f2e1e16a95168febe59f5e38 100755
--- a/tests/cert-emails-test.js
+++ b/tests/cert-emails-test.js
@@ -103,7 +103,7 @@ suite.addBatch({
       assert.strictEqual(r.code, 200);
     },
     "returns a proper cert": function(err, r) {
-      ca.verifyChain([r.body], function(pk) {
+      ca.verifyChain('127.0.0.1', [r.body], function(pk) {
         assert.isTrue(kp.publicKey.equals(pk));
       });
     },
@@ -127,7 +127,7 @@ suite.addBatch({
         topic: function(full_assertion) {
           var cb = this.callback;
           // extract public key at the tail of the chain
-          ca.verifyChain(full_assertion.certificates, function(pk) {
+          ca.verifyChain('127.0.0.1', full_assertion.certificates, function(pk) {
             if (!pk)
               cb(false);
 
diff --git a/tests/lib/start-stop.js b/tests/lib/start-stop.js
index 3f619c527f147c70a9706e2e8dde25af5b4a6abc..d7485e469285845f34916b3f1d7a34675a3f0849 100644
--- a/tests/lib/start-stop.js
+++ b/tests/lib/start-stop.js
@@ -84,16 +84,13 @@ exports.addStartupBatches = function(suite) {
   suite.addBatch({
     "specifying an ephemeral database": {
       topic: function() {
-        if (config.get('database').driver === 'mysql') {
-          process.env['MYSQL_DATABASE_NAME'] = config.get('database').database;
-        } else if (config.get('database').driver === 'json') {
-          process.env['JSON_DATABASE_PATH'] = config.get('database').path;
-        }
+        config.set("database.name", process.env['DATABASE_NAME']);
         return true;
       },
       "should work": function(x) {
-        var cfg = process.env['MYSQL_DATABASE_NAME'] || process.env['JSON_DATABASE_PATH'];
-        assert.equal(typeof cfg, 'string');
+        assert.equal(typeof config.get('database.name'), 'string');
+        assert.equal(typeof process.env['DATABASE_NAME'], 'string');
+        assert.equal(process.env['DATABASE_NAME'], config.get('database.name'));
       }
     }
   });
diff --git a/tests/lib/test_env.js b/tests/lib/test_env.js
index eaeda5d1f719d72f9270d612073846c2af109f80..c45a2fa3f3e344bffe86360bd11d04187ea29e70 100644
--- a/tests/lib/test_env.js
+++ b/tests/lib/test_env.js
@@ -18,8 +18,8 @@ if (undefined === process.env['NODE_ENV']) {
 // if the environment is a 'test_' environment, then we'll use an
 // ephemeral database
 if (process.env['NODE_ENV'] === 'test_mysql') {
-  process.env['MYSQL_DATABASE_NAME'] = "browserid_tmp_" +
+  process.env['DATABASE_NAME'] = "browserid_tmp_" +
     require('../../lib/secrets.js').generate(6);
 } else if (process.env['NODE_ENV'] === 'test_json') {
-  process.env['JSON_DATABASE_PATH'] = require('temp').path({suffix: '.db'});
+  process.env['DATABASE_NAME'] = require('temp').path({suffix: '.db'});
 }