diff --git a/contrib/ast-db-manage/config/versions/f3d1c5d38b56_add_prune_on_boot.py b/contrib/ast-db-manage/config/versions/f3d1c5d38b56_add_prune_on_boot.py
new file mode 100644
index 0000000000000000000000000000000000000000..fe9c35ed1543c90d21346021c6ef9dfb8af67944
--- /dev/null
+++ b/contrib/ast-db-manage/config/versions/f3d1c5d38b56_add_prune_on_boot.py
@@ -0,0 +1,28 @@
+"""add_prune_on_boot
+
+Revision ID: f3d1c5d38b56
+Revises: 44ccced114ce
+Create Date: 2017-08-04 17:31:23.124767
+
+"""
+
+# revision identifiers, used by Alembic.
+revision = 'f3d1c5d38b56'
+down_revision = '44ccced114ce'
+
+from alembic import op
+import sqlalchemy as sa
+
+
+def upgrade():
+    ############################# Enums ##############################
+
+    # yesno_values have already been created, so use postgres enum object
+    # type to get around "already created" issue - works okay with mysql
+    yesno_values = ENUM(*YESNO_VALUES, name=YESNO_NAME, create_type=False)
+
+    op.add_column('ps_contacts', sa.Column('prune_on_boot', yesno_values))
+
+
+def downgrade():
+    op.drop_column('ps_contacts', 'prune_on_boot')
diff --git a/include/asterisk/res_pjsip.h b/include/asterisk/res_pjsip.h
index 31db3676628de9bffd1947437f3c45dc5bec7a17..e2c487aa316e6244e306cb7b02af6b8bd13b4359 100644
--- a/include/asterisk/res_pjsip.h
+++ b/include/asterisk/res_pjsip.h
@@ -270,6 +270,8 @@ struct ast_sip_contact {
 	AST_STRING_FIELD_EXTENDED(call_id);
 	/*! The name of the endpoint that added the contact */
 	AST_STRING_FIELD_EXTENDED(endpoint_name);
+	/*! If true delete the contact on Asterisk restart/boot */
+	int prune_on_boot;
 };
 
 #define CONTACT_STATUS "contact_status"
@@ -1215,6 +1217,9 @@ struct ast_sip_contact *ast_sip_location_retrieve_contact(const char *contact_na
  * \param expiration_time Optional expiration time of the contact
  * \param path_info Path information
  * \param user_agent User-Agent header from REGISTER request
+ * \param via_addr
+ * \param via_port
+ * \param call_id
  * \param endpoint The endpoint that resulted in the contact being added
  *
  * \retval -1 failure
@@ -1238,6 +1243,9 @@ int ast_sip_location_add_contact(struct ast_sip_aor *aor, const char *uri,
  * \param expiration_time Optional expiration time of the contact
  * \param path_info Path information
  * \param user_agent User-Agent header from REGISTER request
+ * \param via_addr
+ * \param via_port
+ * \param call_id
  * \param endpoint The endpoint that resulted in the contact being added
  *
  * \retval -1 failure
@@ -1251,6 +1259,31 @@ int ast_sip_location_add_contact_nolock(struct ast_sip_aor *aor, const char *uri
 	const char *via_addr, int via_port, const char *call_id,
 	struct ast_sip_endpoint *endpoint);
 
+/*!
+ * \brief Create a new contact for an AOR without locking the AOR
+ * \since 13.18.0
+ *
+ * \param aor Pointer to the AOR
+ * \param uri Full contact URI
+ * \param expiration_time Optional expiration time of the contact
+ * \param path_info Path information
+ * \param user_agent User-Agent header from REGISTER request
+ * \param via_addr
+ * \param via_port
+ * \param call_id
+ * \param prune_on_boot Non-zero if the contact cannot survive a restart/boot.
+ * \param endpoint The endpoint that resulted in the contact being added
+ *
+ * \return The created contact or NULL on failure.
+ *
+ * \warning
+ * This function should only be called if you already hold a named write lock on the aor.
+ */
+struct ast_sip_contact *ast_sip_location_create_contact(struct ast_sip_aor *aor,
+	const char *uri, struct timeval expiration_time, const char *path_info,
+	const char *user_agent, const char *via_addr, int via_port, const char *call_id,
+	int prune_on_boot, struct ast_sip_endpoint *endpoint);
+
 /*!
  * \brief Update a contact
  *
@@ -1271,6 +1304,12 @@ int ast_sip_location_update_contact(struct ast_sip_contact *contact);
 */
 int ast_sip_location_delete_contact(struct ast_sip_contact *contact);
 
+/*!
+ * \brief Prune the prune_on_boot contacts
+ * \since 13.18.0
+ */
+void ast_sip_location_prune_boot_contacts(void);
+
 /*!
  * \brief Callback called when an outbound request with authentication credentials is to be sent in dialog
  *
diff --git a/res/res_pjsip.c b/res/res_pjsip.c
index 2917df3c597738332ef24417d8f52012f132a035..ca0c30126eb2a09fdde8f4c022cbb1209c0084d1 100644
--- a/res/res_pjsip.c
+++ b/res/res_pjsip.c
@@ -367,9 +367,12 @@
 				<configOption name="rewrite_contact">
 					<synopsis>Allow Contact header to be rewritten with the source IP address-port</synopsis>
 					<description><para>
-						On inbound SIP messages from this endpoint, the Contact header or an appropriate Record-Route
-						header will be changed to have the source IP address and port. This option does not affect
-						outbound messages sent to this endpoint.
+						On inbound SIP messages from this endpoint, the Contact header or an
+						appropriate Record-Route header will be changed to have the source IP
+						address and port.  This option does not affect outbound messages sent to
+						this endpoint.  This option helps servers communicate with endpoints
+						that are behind NATs.  This option also helps reuse reliable transport
+						connections such as TCP and TLS.
 					</para></description>
 				</configOption>
 				<configOption name="rtp_ipv6" default="no">
@@ -1364,6 +1367,13 @@
 						in incoming SIP REGISTER requests and is not intended to be configured manually.
 					</para></description>
 				</configOption>
+				<configOption name="prune_on_boot">
+					<synopsis>A contact that cannot survive a restart/boot.</synopsis>
+					<description><para>
+						The option is set if the incoming SIP REGISTER contact is rewritten
+						on a reliable transport and is not intended to be configured manually.
+					</para></description>
+				</configOption>
 			</configObject>
 			<configObject name="aor">
 				<synopsis>The configuration for a location of an endpoint</synopsis>
diff --git a/res/res_pjsip/location.c b/res/res_pjsip/location.c
index 6213046e39e2d56c36b2b3195f1c88e22900a890..557aeb6b9ba6b5d317c9d784a43bd9937da07900 100644
--- a/res/res_pjsip/location.c
+++ b/res/res_pjsip/location.c
@@ -356,13 +356,12 @@ struct ast_sip_contact *ast_sip_location_retrieve_contact(const char *contact_na
 	return ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "contact", contact_name);
 }
 
-int ast_sip_location_add_contact_nolock(struct ast_sip_aor *aor, const char *uri,
-		struct timeval expiration_time, const char *path_info, const char *user_agent,
-		const char *via_addr, int via_port, const char *call_id,
-		struct ast_sip_endpoint *endpoint)
+struct ast_sip_contact *ast_sip_location_create_contact(struct ast_sip_aor *aor,
+	const char *uri, struct timeval expiration_time, const char *path_info,
+	const char *user_agent, const char *via_addr, int via_port, const char *call_id,
+	int prune_on_boot, struct ast_sip_endpoint *endpoint)
 {
 	struct ast_sip_contact *contact;
-	int res;
 	char name[MAX_OBJECT_FIELD * 2 + 3];
 	char hash[33];
 
@@ -371,7 +370,7 @@ int ast_sip_location_add_contact_nolock(struct ast_sip_aor *aor, const char *uri
 
 	contact = ast_sorcery_alloc(ast_sip_get_sorcery(), "contact", name);
 	if (!contact) {
-		return -1;
+		return NULL;
 	}
 
 	ast_string_field_set(contact, uri, uri);
@@ -405,14 +404,30 @@ int ast_sip_location_add_contact_nolock(struct ast_sip_aor *aor, const char *uri
 	}
 
 	contact->endpoint = ao2_bump(endpoint);
-
 	if (endpoint) {
 		ast_string_field_set(contact, endpoint_name, ast_sorcery_object_get_id(endpoint));
 	}
 
-	res = ast_sorcery_create(ast_sip_get_sorcery(), contact);
-	ao2_ref(contact, -1);
-	return res;
+	contact->prune_on_boot = prune_on_boot;
+
+	if (ast_sorcery_create(ast_sip_get_sorcery(), contact)) {
+		ao2_ref(contact, -1);
+		return NULL;
+	}
+	return contact;
+}
+
+int ast_sip_location_add_contact_nolock(struct ast_sip_aor *aor, const char *uri,
+		struct timeval expiration_time, const char *path_info, const char *user_agent,
+		const char *via_addr, int via_port, const char *call_id,
+		struct ast_sip_endpoint *endpoint)
+{
+	struct ast_sip_contact *contact;
+
+	contact = ast_sip_location_create_contact(aor, uri, expiration_time, path_info,
+		user_agent, via_addr, via_port, call_id, 0, endpoint);
+	ao2_cleanup(contact);
+	return contact ? 0 : -1;
 }
 
 int ast_sip_location_add_contact(struct ast_sip_aor *aor, const char *uri,
@@ -441,6 +456,29 @@ int ast_sip_location_delete_contact(struct ast_sip_contact *contact)
 	return ast_sorcery_delete(ast_sip_get_sorcery(), contact);
 }
 
+static int prune_boot_contacts_cb(void *obj, void *arg, int flags)
+{
+	struct ast_sip_contact *contact = obj;
+
+	if (contact->prune_on_boot) {
+		ast_sip_location_delete_contact(contact);
+	}
+
+	return 0;
+}
+
+void ast_sip_location_prune_boot_contacts(void)
+{
+	struct ao2_container *contacts;
+
+	contacts = ast_sorcery_retrieve_by_fields(ast_sip_get_sorcery(), "contact",
+		AST_RETRIEVE_FLAG_MULTIPLE | AST_RETRIEVE_FLAG_ALL, NULL);
+	if (contacts) {
+		ao2_callback(contacts, 0, prune_boot_contacts_cb, NULL);
+		ao2_ref(contacts, -1);
+	}
+}
+
 /*! \brief Custom handler for translating from a string timeval to actual structure */
 static int expiration_str2struct(const struct aco_option *opt, struct ast_variable *var, void *obj)
 {
@@ -1221,6 +1259,7 @@ int ast_sip_initialize_sorcery_location(void)
 	ast_sorcery_object_field_register(sorcery, "contact", "via_addr", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_contact, via_addr));
 	ast_sorcery_object_field_register(sorcery, "contact", "via_port", "0", OPT_UINT_T, 0, FLDSET(struct ast_sip_contact, via_port));
 	ast_sorcery_object_field_register(sorcery, "contact", "call_id", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_contact, call_id));
+	ast_sorcery_object_field_register(sorcery, "contact", "prune_on_boot", "no", OPT_YESNO_T, 1, FLDSET(struct ast_sip_contact, prune_on_boot));
 
 	ast_sorcery_object_field_register(sorcery, "aor", "type", "", OPT_NOOP_T, 0, 0);
 	ast_sorcery_object_field_register(sorcery, "aor", "minimum_expiration", "60", OPT_UINT_T, 0, FLDSET(struct ast_sip_aor, minimum_expiration));
diff --git a/res/res_pjsip/pjsip_configuration.c b/res/res_pjsip/pjsip_configuration.c
index d3ff1f32bc133e1b7c567d979d29c0253dbae461..715ffe8ebe4dcba74c50c97790bd81fa7f5952a0 100644
--- a/res/res_pjsip/pjsip_configuration.c
+++ b/res/res_pjsip/pjsip_configuration.c
@@ -2057,6 +2057,8 @@ int ast_res_pjsip_initialize_configuration(void)
 
 	load_all_endpoints();
 
+	ast_sip_location_prune_boot_contacts();
+
 	return 0;
 }
 
diff --git a/res/res_pjsip_registrar.c b/res/res_pjsip_registrar.c
index a4ce54769f79f6a1baf52fa175251d424305136e..ba1c074b3697e8cbbcdef1981cc2e04001c3a833 100644
--- a/res/res_pjsip_registrar.c
+++ b/res/res_pjsip_registrar.c
@@ -310,6 +310,47 @@ static int registrar_validate_path(pjsip_rx_data *rdata, struct ast_sip_aor *aor
 	return -1;
 }
 
+/*! Transport monitor for incoming REGISTER contacts */
+struct contact_transport_monitor {
+	/*!
+	 * \brief Sorcery contact name to remove on transport shutdown
+	 * \note Stored after aor_name in space reserved when struct allocated.
+	 */
+	char *contact_name;
+	/*! AOR name the contact is associated */
+	char aor_name[0];
+};
+
+static void register_contact_transport_shutdown_cb(void *data)
+{
+	struct contact_transport_monitor *monitor = data;
+	struct ast_sip_contact *contact;
+	struct ast_sip_aor *aor;
+
+	aor = ast_sip_location_retrieve_aor(monitor->aor_name);
+	if (!aor) {
+		return;
+	}
+
+	ao2_lock(aor);
+	contact = ast_sip_location_retrieve_contact(monitor->contact_name);
+	if (contact) {
+		ast_sip_location_delete_contact(contact);
+		ast_verb(3, "Removed contact '%s' from AOR '%s' due to transport shutdown\n",
+			contact->uri, monitor->aor_name);
+		ast_test_suite_event_notify("AOR_CONTACT_REMOVED",
+			"Contact: %s\r\n"
+			"AOR: %s\r\n"
+			"UserAgent: %s",
+			contact->uri,
+			monitor->aor_name,
+			contact->user_agent);
+		ao2_ref(contact, -1);
+	}
+	ao2_unlock(aor);
+	ao2_ref(aor, -1);
+}
+
 static int register_aor_core(pjsip_rx_data *rdata,
 	struct ast_sip_endpoint *endpoint,
 	struct ast_sip_aor *aor,
@@ -419,6 +460,9 @@ static int register_aor_core(pjsip_rx_data *rdata,
 		pjsip_uri_print(PJSIP_URI_IN_CONTACT_HDR, details.uri, contact_uri, sizeof(contact_uri));
 
 		if (!(contact = ao2_callback(contacts, OBJ_UNLINK, registrar_find_contact, &details))) {
+			int prune_on_boot = 0;
+			pj_str_t host_name;
+
 			/* If they are actually trying to delete a contact that does not exist... be forgiving */
 			if (!expiration) {
 				ast_verb(3, "Attempted to remove non-existent contact '%s' from AOR '%s' by request\n",
@@ -426,14 +470,68 @@ static int register_aor_core(pjsip_rx_data *rdata,
 				continue;
 			}
 
-			if (ast_sip_location_add_contact_nolock(aor, contact_uri, ast_tvadd(ast_tvnow(),
-				ast_samp2tv(expiration, 1)), path_str ? ast_str_buffer(path_str) : NULL,
-					user_agent, via_addr, via_port, call_id, endpoint)) {
+			/* Determine if the contact cannot survive a restart/boot. */
+			if (details.uri->port == rdata->pkt_info.src_port
+				&& !pj_strcmp(&details.uri->host,
+					pj_cstr(&host_name, rdata->pkt_info.src_name))
+				/* We have already checked if the URI scheme is sip: or sips: */
+				&& PJSIP_TRANSPORT_IS_RELIABLE(rdata->tp_info.transport)) {
+				pj_str_t type_name;
+
+				/* Determine the transport parameter value */
+				if (!strcasecmp("WSS", rdata->tp_info.transport->type_name)) {
+					/* WSS is special, as it needs to be ws. */
+					pj_cstr(&type_name, "ws");
+				} else {
+					pj_cstr(&type_name, rdata->tp_info.transport->type_name);
+				}
+
+				if (!pj_stricmp(&details.uri->transport_param, &type_name)
+					&& (endpoint->nat.rewrite_contact
+						/* Websockets are always rewritten */
+						|| !pj_stricmp(&details.uri->transport_param,
+							pj_cstr(&type_name, "ws")))) {
+					/*
+					 * The contact was rewritten to the reliable transport's
+					 * source address.  Disconnecting the transport for any
+					 * reason invalidates the contact.
+					 */
+					prune_on_boot = 1;
+				}
+			}
+
+			contact = ast_sip_location_create_contact(aor, contact_uri,
+				ast_tvadd(ast_tvnow(), ast_samp2tv(expiration, 1)),
+				path_str ? ast_str_buffer(path_str) : NULL,
+				user_agent, via_addr, via_port, call_id, prune_on_boot, endpoint);
+			if (!contact) {
 				ast_log(LOG_ERROR, "Unable to bind contact '%s' to AOR '%s'\n",
-						contact_uri, aor_name);
+					contact_uri, aor_name);
 				continue;
 			}
 
+			if (prune_on_boot) {
+				const char *contact_name;
+				struct contact_transport_monitor *monitor;
+
+				/*
+				 * Monitor the transport in case it gets disconnected because
+				 * the contact won't be valid anymore if that happens.
+				 */
+				contact_name = ast_sorcery_object_get_id(contact);
+				monitor = ao2_alloc_options(sizeof(*monitor) + 2 + strlen(aor_name)
+					+ strlen(contact_name), NULL, AO2_ALLOC_OPT_LOCK_NOLOCK);
+				if (monitor) {
+					strcpy(monitor->aor_name, aor_name);/* Safe */
+					monitor->contact_name = monitor->aor_name + strlen(aor_name) + 1;
+					strcpy(monitor->contact_name, contact_name);/* Safe */
+
+					ast_sip_transport_monitor_register(rdata->tp_info.transport,
+						register_contact_transport_shutdown_cb, monitor);
+					ao2_ref(monitor, -1);
+				}
+			}
+
 			ast_verb(3, "Added contact '%s' to AOR '%s' with expiration of %d seconds\n",
 				contact_uri, aor_name, expiration);
 			ast_test_suite_event_notify("AOR_CONTACT_ADDED",
@@ -885,6 +983,7 @@ static int unload_module(void)
 	ast_manager_unregister(AMI_SHOW_REGISTRATIONS);
 	ast_manager_unregister(AMI_SHOW_REGISTRATION_CONTACT_STATUSES);
 	ast_sip_unregister_service(&registrar_module);
+	ast_sip_transport_monitor_unregister_all(register_contact_transport_shutdown_cb);
 	return 0;
 }
 
diff --git a/res/res_pjsip_transport_websocket.c b/res/res_pjsip_transport_websocket.c
index 1429cceed8b1c3d1d1bc1dfd5fefbb8d087d80d1..22ec19540dc52839f266aaf75495accb0ff72bbe 100644
--- a/res/res_pjsip_transport_websocket.c
+++ b/res/res_pjsip_transport_websocket.c
@@ -145,6 +145,7 @@ static int transport_create(void *data)
 {
 	struct transport_create_data *create_data = data;
 	struct ws_transport *newtransport = NULL;
+	pjsip_tp_state_callback state_cb;
 
 	pjsip_endpoint *endpt = ast_sip_get_pjsip_endpoint();
 	struct pjsip_tpmgr *tpmgr = pjsip_endpt_get_tpmgr(endpt);
@@ -161,6 +162,10 @@ static int transport_create(void *data)
 		goto on_error;
 	}
 
+	/* Give websocket transport a unique name for its lifetime */
+	snprintf(newtransport->transport.obj_name, PJ_MAX_OBJ_NAME, "ws%p",
+		&newtransport->transport);
+
 	newtransport->transport.endpt = endpt;
 
 	if (!(pool = pjsip_endpt_create_pool(endpt, "ws", 512, 512))) {
@@ -219,6 +224,7 @@ static int transport_create(void *data)
 	newtransport->transport.flag = pjsip_transport_get_flag_from_type((pjsip_transport_type_e)newtransport->transport.key.type);
 	newtransport->transport.info = (char *)pj_pool_alloc(newtransport->transport.pool, 64);
 
+	newtransport->transport.dir = PJSIP_TP_DIR_INCOMING;
 	newtransport->transport.tpmgr = tpmgr;
 	newtransport->transport.send_msg = &ws_send_msg;
 	newtransport->transport.destroy = &ws_destroy;
@@ -242,6 +248,16 @@ static int transport_create(void *data)
 	}
 
 	create_data->transport = newtransport;
+
+	/* Notify application of transport state */
+	state_cb = pjsip_tpmgr_get_state_cb(newtransport->transport.tpmgr);
+	if (state_cb) {
+		pjsip_transport_state_info state_info;
+
+		memset(&state_info, 0, sizeof(state_info));
+		state_cb(&newtransport->transport, PJSIP_TP_STATE_CONNECTED, &state_info);
+	}
+
 	return 0;
 
 on_error: