diff --git a/services/tunnelbroker/src/constants.rs b/services/tunnelbroker/src/constants.rs
--- a/services/tunnelbroker/src/constants.rs
+++ b/services/tunnelbroker/src/constants.rs
@@ -21,6 +21,8 @@
   tracing_subscriber::filter::EnvFilter::DEFAULT_ENV;
 pub const FCM_ACCESS_TOKEN_GENERATION_THRESHOLD: u64 = 5 * 60;
 
+pub const PUSH_SERVICE_REQUEST_TIMEOUT: Duration = Duration::from_secs(8);
+
 pub mod dynamodb {
   // This table holds messages which could not be immediately delivered to
   // a device.
diff --git a/services/tunnelbroker/src/notifs/apns/mod.rs b/services/tunnelbroker/src/notifs/apns/mod.rs
--- a/services/tunnelbroker/src/notifs/apns/mod.rs
+++ b/services/tunnelbroker/src/notifs/apns/mod.rs
@@ -1,3 +1,4 @@
+use crate::constants::PUSH_SERVICE_REQUEST_TIMEOUT;
 use crate::notifs::apns::config::APNsConfig;
 use crate::notifs::apns::error::Error::ResponseError;
 use crate::notifs::apns::headers::{NotificationHeaders, PushType};
@@ -38,6 +39,7 @@
       .http2_prior_knowledge()
       .http2_keep_alive_interval(Some(Duration::from_secs(5)))
       .http2_keep_alive_while_idle(true)
+      .timeout(PUSH_SERVICE_REQUEST_TIMEOUT)
       .build()?;
 
     Ok(APNsClient {
diff --git a/services/tunnelbroker/src/notifs/fcm/mod.rs b/services/tunnelbroker/src/notifs/fcm/mod.rs
--- a/services/tunnelbroker/src/notifs/fcm/mod.rs
+++ b/services/tunnelbroker/src/notifs/fcm/mod.rs
@@ -1,3 +1,4 @@
+use crate::constants::PUSH_SERVICE_REQUEST_TIMEOUT;
 use crate::notifs::fcm::config::FCMConfig;
 use crate::notifs::fcm::error::Error::FCMError;
 use crate::notifs::fcm::firebase_message::{FCMMessage, FCMMessageWrapper};
@@ -23,7 +24,9 @@
 
 impl FCMClient {
   pub fn new(config: &FCMConfig) -> Result<Self, error::Error> {
-    let http_client = reqwest::Client::builder().build()?;
+    let http_client = reqwest::Client::builder()
+      .timeout(PUSH_SERVICE_REQUEST_TIMEOUT)
+      .build()?;
 
     // Token must be a short-lived token (60 minutes) and in a reasonable
     // timeframe.
diff --git a/services/tunnelbroker/src/notifs/web_push/mod.rs b/services/tunnelbroker/src/notifs/web_push/mod.rs
--- a/services/tunnelbroker/src/notifs/web_push/mod.rs
+++ b/services/tunnelbroker/src/notifs/web_push/mod.rs
@@ -1,3 +1,4 @@
+use crate::constants::PUSH_SERVICE_REQUEST_TIMEOUT;
 use serde::{Deserialize, Serialize};
 use web_push::{
   ContentEncoding, HyperWebPushClient, SubscriptionInfo, VapidSignatureBuilder,
@@ -53,7 +54,13 @@
     builder.set_vapid_signature(vapid_signature);
 
     let message = builder.build()?;
-    self.inner_client.send(message).await?;
+    let response_future = self.inner_client.send(message);
+
+    tokio::time::timeout(PUSH_SERVICE_REQUEST_TIMEOUT, response_future)
+      .await
+      .map_err(|err| {
+        error::Error::WebPush(web_push::WebPushError::Other(err.to_string()))
+      })??;
 
     Ok(())
   }
diff --git a/services/tunnelbroker/src/notifs/wns/mod.rs b/services/tunnelbroker/src/notifs/wns/mod.rs
--- a/services/tunnelbroker/src/notifs/wns/mod.rs
+++ b/services/tunnelbroker/src/notifs/wns/mod.rs
@@ -1,3 +1,4 @@
+use crate::constants::PUSH_SERVICE_REQUEST_TIMEOUT;
 use crate::notifs::wns::config::WNSConfig;
 use error::WNSTokenError;
 use reqwest::StatusCode;
@@ -32,7 +33,9 @@
 
 impl WNSClient {
   pub fn new(config: &WNSConfig) -> Result<Self, error::Error> {
-    let http_client = reqwest::Client::builder().build()?;
+    let http_client = reqwest::Client::builder()
+      .timeout(PUSH_SERVICE_REQUEST_TIMEOUT)
+      .build()?;
     Ok(WNSClient {
       http_client,
       config: config.clone(),