diff --git a/nav2_behavior_tree/include/nav2_behavior_tree/back_up_action.hpp b/nav2_behavior_tree/include/nav2_behavior_tree/back_up_action.hpp
index abc421cbc197cc838d380f2e5e5c1df71f57e592..8eb8518cabf0d4fdfd50c0037e7965acdfba8d1a 100644
--- a/nav2_behavior_tree/include/nav2_behavior_tree/back_up_action.hpp
+++ b/nav2_behavior_tree/include/nav2_behavior_tree/back_up_action.hpp
@@ -52,10 +52,10 @@ public:
 
   static BT::PortsList providedPorts()
   {
-    return {
-      BT::InputPort<double>("backup_dist", -0.15, "Distance to backup"),
-      BT::InputPort<double>("backup_speed", 0.025, "Speed at which to backup")
-    };
+    return providedBasicPorts({
+        BT::InputPort<double>("backup_dist", -0.15, "Distance to backup"),
+        BT::InputPort<double>("backup_speed", 0.025, "Speed at which to backup")
+      });
   }
 };
 
diff --git a/nav2_behavior_tree/include/nav2_behavior_tree/bt_action_node.hpp b/nav2_behavior_tree/include/nav2_behavior_tree/bt_action_node.hpp
index 9208cd2284fa7092fc4b5a06d68fdb1dc3ffc3a3..e069794f104af7780c20630f48822bcf10bcc424 100644
--- a/nav2_behavior_tree/include/nav2_behavior_tree/bt_action_node.hpp
+++ b/nav2_behavior_tree/include/nav2_behavior_tree/bt_action_node.hpp
@@ -41,8 +41,9 @@ public:
     result_ = typename rclcpp_action::ClientGoalHandle<ActionT>::WrappedResult();
 
     // Get the required items from the blackboard
-    node_loop_timeout_ =
-      config().blackboard->get<std::chrono::milliseconds>("node_loop_timeout");
+    server_timeout_ =
+      config().blackboard->get<std::chrono::milliseconds>("server_timeout");
+    getInput<std::chrono::milliseconds>("server_timeout", server_timeout_);
 
     // Now that we have the ROS node to use, create the action client for this BT action
     action_client_ = rclcpp_action::create_client<ActionT>(node_, action_name_);
@@ -61,8 +62,25 @@ public:
   {
   }
 
+  // Any subclass of BtActionNode that accepts parameters must provide a providedPorts method
+  // and call providedBasicPorts in it.
+  static BT::PortsList providedBasicPorts(BT::PortsList addition)
+  {
+    BT::PortsList basic = {
+      BT::InputPort<std::chrono::milliseconds>("server_timeout")
+    };
+    basic.insert(addition.begin(), addition.end());
+
+    return basic;
+  }
+
+  static BT::PortsList providedPorts()
+  {
+    return providedBasicPorts({});
+  }
+
   // Derived classes can override any of the following methods to hook into the
-  // processing for the action: on_tick, on_loop_timeout, and on_success
+  // processing for the action: on_tick, on_server_timeout, and on_success
 
   // Could do dynamic checks, such as getting updates to values on the blackboard
   virtual void on_tick()
@@ -71,7 +89,7 @@ public:
 
   // There can be many loop iterations per tick. Any opportunity to do something after
   // a timeout waiting for a result that hasn't been received yet
-  virtual void on_loop_timeout()
+  virtual void on_server_timeout()
   {
   }
 
@@ -106,9 +124,9 @@ new_goal_received:
     auto future_result = goal_handle_->async_result();
     rclcpp::executor::FutureReturnCode rc;
     do {
-      rc = rclcpp::spin_until_future_complete(node_, future_result, node_loop_timeout_);
+      rc = rclcpp::spin_until_future_complete(node_, future_result, server_timeout_);
       if (rc == rclcpp::executor::FutureReturnCode::TIMEOUT) {
-        on_loop_timeout();
+        on_server_timeout();
 
         // We can handle a new goal if we're still executing
         auto status = goal_handle_->get_status();
@@ -197,7 +215,7 @@ protected:
 
   // The timeout value while to use in the tick loop while waiting for
   // a result from the server
-  std::chrono::milliseconds node_loop_timeout_;
+  std::chrono::milliseconds server_timeout_;
 };
 
 }  // namespace nav2_behavior_tree
diff --git a/nav2_behavior_tree/include/nav2_behavior_tree/bt_conversions.hpp b/nav2_behavior_tree/include/nav2_behavior_tree/bt_conversions.hpp
index ead7ed686a3d74306265f60b076e079b131efd5d..6b7d783511b15876a0dffa14e9ae735f1748e5e6 100644
--- a/nav2_behavior_tree/include/nav2_behavior_tree/bt_conversions.hpp
+++ b/nav2_behavior_tree/include/nav2_behavior_tree/bt_conversions.hpp
@@ -59,6 +59,12 @@ inline geometry_msgs::msg::Quaternion convertFromString(const StringView key)
   }
 }
 
+template<>
+inline std::chrono::milliseconds convertFromString<std::chrono::milliseconds>(const StringView key)
+{
+  return std::chrono::milliseconds(std::stoul(key.data()));
+}
+
 }  // namespace BT
 
 #endif  // NAV2_BEHAVIOR_TREE__BT_CONVERSIONS_HPP_
diff --git a/nav2_behavior_tree/include/nav2_behavior_tree/bt_service_node.hpp b/nav2_behavior_tree/include/nav2_behavior_tree/bt_service_node.hpp
index 8f40866bfcd40eef08652a71087140531efb807d..76fbdcdfb11235ac192cc63b8d947a7dc9f4beff 100644
--- a/nav2_behavior_tree/include/nav2_behavior_tree/bt_service_node.hpp
+++ b/nav2_behavior_tree/include/nav2_behavior_tree/bt_service_node.hpp
@@ -37,8 +37,9 @@ public:
     node_ = config().blackboard->get<rclcpp::Node::SharedPtr>("node");
 
     // Get the required items from the blackboard
-    node_loop_timeout_ =
-      config().blackboard->get<std::chrono::milliseconds>("node_loop_timeout");
+    server_timeout_ =
+      config().blackboard->get<std::chrono::milliseconds>("server_timeout");
+    getInput<std::chrono::milliseconds>("server_timeout", server_timeout_);
 
     // Now that we have node_ to use, create the service client for this BT service
     getInput("service_name", service_name_);
@@ -59,12 +60,22 @@ public:
   {
   }
 
-  // Any BT node that accepts parameters must provide a requiredNodeParameters method
-  static BT::PortsList providedPorts()
+  // Any subclass of BtServiceNode that accepts parameters must provide a providedPorts method
+  // and call providedBasicPorts in it.
+  static BT::PortsList providedBasicPorts(BT::PortsList addition)
   {
-    return {
-      BT::InputPort<std::string>("service_name", "please_set_service_name_in_BT_Node")
+    BT::PortsList basic = {
+      BT::InputPort<std::string>("service_name", "please_set_service_name_in_BT_Node"),
+      BT::InputPort<std::chrono::milliseconds>("server_timeout")
     };
+    basic.insert(addition.begin(), addition.end());
+
+    return basic;
+  }
+
+  static BT::PortsList providedPorts()
+  {
+    return providedBasicPorts({});
   }
 
   // The main override required by a BT service
@@ -75,12 +86,15 @@ public:
 
     rclcpp::executor::FutureReturnCode rc;
     rc = rclcpp::spin_until_future_complete(node_,
-        future_result, node_loop_timeout_);
-    if (rc != rclcpp::executor::FutureReturnCode::SUCCESS) {
-      return BT::NodeStatus::FAILURE;
-    } else {
+        future_result, server_timeout_);
+    if (rc == rclcpp::executor::FutureReturnCode::SUCCESS) {
       return BT::NodeStatus::SUCCESS;
+    } else if (rc == rclcpp::executor::FutureReturnCode::TIMEOUT) {
+      RCLCPP_WARN(node_->get_logger(),
+        "Node timed out while executing service call to %s.", service_name_.c_str());
+      on_server_timeout();
     }
+    return BT::NodeStatus::FAILURE;
   }
 
   // Fill in service request with information if necessary
@@ -89,6 +103,12 @@ public:
     request_ = std::make_shared<typename ServiceT::Request>();
   }
 
+  // An opportunity to do something after
+  // a timeout waiting for a result that hasn't been received yet
+  virtual void on_server_timeout()
+  {
+  }
+
 protected:
   std::string service_name_, service_node_name_;
   typename std::shared_ptr<rclcpp::Client<ServiceT>> service_client_;
@@ -99,7 +119,7 @@ protected:
 
   // The timeout value while to use in the tick loop while waiting for
   // a result from the server
-  std::chrono::milliseconds node_loop_timeout_;
+  std::chrono::milliseconds server_timeout_;
 };
 
 }  // namespace nav2_behavior_tree
diff --git a/nav2_behavior_tree/include/nav2_behavior_tree/compute_path_to_pose_action.hpp b/nav2_behavior_tree/include/nav2_behavior_tree/compute_path_to_pose_action.hpp
index 91e8bc488274641e1dd8dece446c97dcbeb93cf4..d610563b0f41631878ace76c2c07efeff20ca54b 100644
--- a/nav2_behavior_tree/include/nav2_behavior_tree/compute_path_to_pose_action.hpp
+++ b/nav2_behavior_tree/include/nav2_behavior_tree/compute_path_to_pose_action.hpp
@@ -53,10 +53,10 @@ public:
 
   static BT::PortsList providedPorts()
   {
-    return {
-      BT::OutputPort<nav_msgs::msg::Path>("path", "Path created by ComputePathToPose node"),
-      BT::InputPort<geometry_msgs::msg::PoseStamped>("goal", "Destination to plan to")
-    };
+    return providedBasicPorts({
+        BT::OutputPort<nav_msgs::msg::Path>("path", "Path created by ComputePathToPose node"),
+        BT::InputPort<geometry_msgs::msg::PoseStamped>("goal", "Destination to plan to")
+      });
   }
 
 private:
diff --git a/nav2_behavior_tree/include/nav2_behavior_tree/follow_path_action.hpp b/nav2_behavior_tree/include/nav2_behavior_tree/follow_path_action.hpp
index ecbe918ba9d8cb14bdd363b9bdc7f4e4e97dc2b9..023cb3780bbae0a097aaa47305d74e0b6b107342 100644
--- a/nav2_behavior_tree/include/nav2_behavior_tree/follow_path_action.hpp
+++ b/nav2_behavior_tree/include/nav2_behavior_tree/follow_path_action.hpp
@@ -40,7 +40,7 @@ public:
     getInput("path", goal_.path);
   }
 
-  void on_loop_timeout() override
+  void on_server_timeout() override
   {
     // Check if the goal has been updated
     if (config().blackboard->get<bool>("path_updated")) {
@@ -56,9 +56,9 @@ public:
 
   static BT::PortsList providedPorts()
   {
-    return {
-      BT::InputPort<nav_msgs::msg::Path>("path", "Path to follow"),
-    };
+    return providedBasicPorts({
+        BT::InputPort<nav_msgs::msg::Path>("path", "Path to follow"),
+      });
   }
 };
 
diff --git a/nav2_behavior_tree/include/nav2_behavior_tree/navigate_to_pose_action.hpp b/nav2_behavior_tree/include/nav2_behavior_tree/navigate_to_pose_action.hpp
index e3dca52f37c773cb67548144fe8da15899b8431b..46f40fec5530664ab1d1b306a79167a57ff19c30 100644
--- a/nav2_behavior_tree/include/nav2_behavior_tree/navigate_to_pose_action.hpp
+++ b/nav2_behavior_tree/include/nav2_behavior_tree/navigate_to_pose_action.hpp
@@ -58,10 +58,10 @@ public:
   // Any BT node that accepts parameters must provide a requiredNodeParameters method
   static BT::PortsList providedPorts()
   {
-    return {
-      BT::InputPort<geometry_msgs::msg::Point>("position", "0;0;0", "Position"),
-      BT::InputPort<geometry_msgs::msg::Quaternion>("orientation", "0;0;0;0", "Orientation")
-    };
+    return providedBasicPorts({
+        BT::InputPort<geometry_msgs::msg::Point>("position", "0;0;0", "Position"),
+        BT::InputPort<geometry_msgs::msg::Quaternion>("orientation", "0;0;0;0", "Orientation")
+      });
   }
 };
 
diff --git a/nav2_behavior_tree/include/nav2_behavior_tree/spin_action.hpp b/nav2_behavior_tree/include/nav2_behavior_tree/spin_action.hpp
index 38c11dc3619d2bb4dcf70544f4e1ba4b3daa193f..2324a917e434b2fce958116d58d780dc56f301dd 100644
--- a/nav2_behavior_tree/include/nav2_behavior_tree/spin_action.hpp
+++ b/nav2_behavior_tree/include/nav2_behavior_tree/spin_action.hpp
@@ -43,9 +43,9 @@ public:
 
   static BT::PortsList providedPorts()
   {
-    return {
-      BT::InputPort<double>("spin_dist", 1.57, "Spin distance")
-    };
+    return providedBasicPorts({
+        BT::InputPort<double>("spin_dist", 1.57, "Spin distance")
+      });
   }
 };
 
diff --git a/nav2_behavior_tree/include/nav2_behavior_tree/wait_action.hpp b/nav2_behavior_tree/include/nav2_behavior_tree/wait_action.hpp
index f31a8c28bf1e60935e0f9d0282968f8b95e54d08..1bd4997f72fb62994b3fa49d52cbd44fedff992a 100644
--- a/nav2_behavior_tree/include/nav2_behavior_tree/wait_action.hpp
+++ b/nav2_behavior_tree/include/nav2_behavior_tree/wait_action.hpp
@@ -47,9 +47,9 @@ public:
   // Any BT node that accepts parameters must provide a requiredNodeParameters method
   static BT::PortsList providedPorts()
   {
-    return {
-      BT::InputPort<int>("wait_duration", 1, "Wait time")
-    };
+    return providedBasicPorts({
+        BT::InputPort<int>("wait_duration", 1, "Wait time")
+      });
   }
 };
 
diff --git a/nav2_bt_navigator/src/bt_navigator.cpp b/nav2_bt_navigator/src/bt_navigator.cpp
index c06b2ea2206b67771f50db5960c27177e5a00f1a..9a9a0ad1e5221a93ffd45ff98a7f382475c3599f 100644
--- a/nav2_bt_navigator/src/bt_navigator.cpp
+++ b/nav2_bt_navigator/src/bt_navigator.cpp
@@ -82,7 +82,7 @@ BtNavigator::on_configure(const rclcpp_lifecycle::State & /*state*/)
   // Put items on the blackboard
   blackboard_->set<rclcpp::Node::SharedPtr>("node", client_node_);  // NOLINT
   blackboard_->set<std::shared_ptr<tf2_ros::Buffer>>("tf_buffer", tf_);  // NOLINT
-  blackboard_->set<std::chrono::milliseconds>("node_loop_timeout", std::chrono::milliseconds(10));  // NOLINT
+  blackboard_->set<std::chrono::milliseconds>("server_timeout", std::chrono::milliseconds(10));  // NOLINT
   blackboard_->set<bool>("path_updated", false);  // NOLINT
   blackboard_->set<bool>("initial_pose_received", false);  // NOLINT