diff --git a/node/Multicaster.cpp b/node/Multicaster.cpp
index 4856b88ee..d93b2ae10 100644
--- a/node/Multicaster.cpp
+++ b/node/Multicaster.cpp
@@ -268,7 +268,8 @@ void Multicaster::send(
 
 			const unsigned int gatherLimit = (limit - (unsigned int)gs.members.size()) + 1;
 
-			if ((gs.members.empty())||((now - gs.lastExplicitGather) >= ZT_MULTICAST_EXPLICIT_GATHER_DELAY)) {
+			int timerScale = RR->node->lowBandwidthModeEnabled() ? 3 : 1;
+			if ((gs.members.empty())||((now - gs.lastExplicitGather) >= (ZT_MULTICAST_EXPLICIT_GATHER_DELAY * timerScale))) {
 				gs.lastExplicitGather = now;
 
 				Address explicitGatherPeers[16];
diff --git a/node/Node.cpp b/node/Node.cpp
index 019a8afca..7bdf331dc 100644
--- a/node/Node.cpp
+++ b/node/Node.cpp
@@ -202,6 +202,14 @@ public:
 	{
 		const std::vector<InetAddress> *const alwaysContactEndpoints = _alwaysContact.get(p->address());
 		if (alwaysContactEndpoints) {
+
+			// Contact upstream peers as infrequently as possible
+			ZT_PeerRole role = RR->topology->role(p->address());
+			int roleBasedTimerScale = (role == ZT_PEER_ROLE_LEAF) ? 2 : 16;
+			if ((RR->node->now() - p->lastSentFullHello()) <= (ZT_PATH_HEARTBEAT_PERIOD * roleBasedTimerScale)) {
+				return;
+			}
+
 			const unsigned int sent = p->doPingAndKeepalive(_tPtr,_now);
 			bool contacted = (sent != 0);
 
@@ -262,7 +270,7 @@ ZT_ResultCode Node::processBackgroundTasks(void *tptr,int64_t now,volatile int64
 		}
 	}
 
-	unsigned long timeUntilNextPingCheck = ZT_PING_CHECK_INVERVAL;
+	unsigned long timeUntilNextPingCheck = _lowBandwidthMode ? (ZT_PING_CHECK_INVERVAL * 5) : ZT_PING_CHECK_INVERVAL;
 	const int64_t timeSinceLastPingCheck = now - _lastPingCheck;
 	if (timeSinceLastPingCheck >= timeUntilNextPingCheck) {
 		try {
@@ -309,6 +317,7 @@ ZT_ResultCode Node::processBackgroundTasks(void *tptr,int64_t now,volatile int64
 
 			// Get peers we should stay connected to according to network configs
 			// Also get networks and whether they need config so we only have to do one pass over networks
+			int timerScale = _lowBandwidthMode ? 64 : 1;
 			std::vector< std::pair< SharedPtr<Network>,bool > > networkConfigNeeded;
 			{
 				Mutex::Lock l(_networks_m);
@@ -317,7 +326,7 @@ ZT_ResultCode Node::processBackgroundTasks(void *tptr,int64_t now,volatile int64
 				SharedPtr<Network> *network = (SharedPtr<Network> *)0;
 				while (i.next(nwid,network)) {
 					(*network)->config().alwaysContactAddresses(alwaysContact);
-					networkConfigNeeded.push_back( std::pair< SharedPtr<Network>,bool >(*network,(((now - (*network)->lastConfigUpdate()) >= ZT_NETWORK_AUTOCONF_DELAY)||(!(*network)->hasConfig()))) );
+					networkConfigNeeded.push_back( std::pair< SharedPtr<Network>,bool >(*network,(((now - (*network)->lastConfigUpdate()) >= ZT_NETWORK_AUTOCONF_DELAY * timerScale)||(!(*network)->hasConfig()))) );
 				}
 			}
 
@@ -336,9 +345,12 @@ ZT_ResultCode Node::processBackgroundTasks(void *tptr,int64_t now,volatile int64
 
 			// Refresh network config or broadcast network updates to members as needed
 			for(std::vector< std::pair< SharedPtr<Network>,bool > >::const_iterator n(networkConfigNeeded.begin());n!=networkConfigNeeded.end();++n) {
-				if (n->second)
+				if (n->second) {
 					n->first->requestConfiguration(tptr);
-				n->first->sendUpdatesToMembers(tptr);
+				}
+				if (! _lowBandwidthMode) {
+					n->first->sendUpdatesToMembers(tptr);
+				}
 			}
 
 			// Update online status, post status change as event
diff --git a/node/Node.hpp b/node/Node.hpp
index 834f50cc9..0ddd5cb7b 100644
--- a/node/Node.hpp
+++ b/node/Node.hpp
@@ -269,6 +269,16 @@ public:
 		_stats.inVerbBytes[v] += (uint64_t)bytes;
 	}
 
+	inline void setLowBandwidthMode(bool isEnabled)
+	{
+		_lowBandwidthMode = isEnabled;
+	}
+
+	inline bool lowBandwidthModeEnabled()
+	{
+		return _lowBandwidthMode;
+	}
+
 private:
 	RuntimeEnvironment _RR;
 	RuntimeEnvironment *RR;
@@ -316,6 +326,7 @@ private:
 	int64_t _lastMemoizedTraceSettings;
 	volatile int64_t _prngState[2];
 	bool _online;
+	bool _lowBandwidthMode;
 };
 
 } // namespace ZeroTier
diff --git a/node/Peer.cpp b/node/Peer.cpp
index 99fa8d277..c1dc124c6 100644
--- a/node/Peer.cpp
+++ b/node/Peer.cpp
@@ -219,11 +219,15 @@ void Peer::received(
 	// is done less frequently.
 	if (this->trustEstablished(now)) {
 		const int64_t sinceLastPush = now - _lastDirectPathPushSent;
-		if (sinceLastPush >= ((hops == 0) ? ZT_DIRECT_PATH_PUSH_INTERVAL_HAVEPATH : ZT_DIRECT_PATH_PUSH_INTERVAL)) {
+		bool lowBandwidth = RR->node->lowBandwidthModeEnabled();
+		int timerScale = lowBandwidth ? 16 : 1;
+		if (sinceLastPush >= ((hops == 0) ? ZT_DIRECT_PATH_PUSH_INTERVAL_HAVEPATH * timerScale : ZT_DIRECT_PATH_PUSH_INTERVAL)) {
 			_lastDirectPathPushSent = now;
 			std::vector<InetAddress> pathsToPush(RR->node->directPaths());
-			std::vector<InetAddress> ma = RR->sa->whoami();
-			pathsToPush.insert(pathsToPush.end(), ma.begin(), ma.end());
+			if (! lowBandwidth) {
+				std::vector<InetAddress> ma = RR->sa->whoami();
+				pathsToPush.insert(pathsToPush.end(), ma.begin(), ma.end());
+			}
 			if (!pathsToPush.empty()) {
 				std::vector<InetAddress>::const_iterator p(pathsToPush.begin());
 				while (p != pathsToPush.end()) {
@@ -453,7 +457,7 @@ void Peer::sendHELLO(void *tPtr,const int64_t localSocket,const InetAddress &atA
 	if (atAddress) {
 		outp.armor(_key,false,nullptr); // false == don't encrypt full payload, but add MAC
 		RR->node->expectReplyTo(outp.packetId());
-		RR->node->putPacket(tPtr,-1,atAddress,outp.data(),outp.size());
+		RR->node->putPacket(tPtr,RR->node->lowBandwidthModeEnabled() ? localSocket : -1,atAddress,outp.data(),outp.size());
 	} else {
 		RR->node->expectReplyTo(outp.packetId());
 		RR->sw->send(tPtr,outp,false); // false == don't encrypt full payload, but add MAC
@@ -477,8 +481,9 @@ void Peer::tryMemorizedPath(void *tPtr,int64_t now)
 	if ((now - _lastTriedMemorizedPath) >= ZT_TRY_MEMORIZED_PATH_INTERVAL) {
 		_lastTriedMemorizedPath = now;
 		InetAddress mp;
-		if (RR->node->externalPathLookup(tPtr,_id.address(),-1,mp))
+		if (RR->node->externalPathLookup(tPtr,_id.address(),-1,mp)) {
 			attemptToContactAt(tPtr,-1,mp,now,true);
+		}
 	}
 }
 
diff --git a/node/Peer.hpp b/node/Peer.hpp
index 0192143e3..668bbbee9 100644
--- a/node/Peer.hpp
+++ b/node/Peer.hpp
@@ -302,6 +302,8 @@ public:
 	 */
 	inline int64_t isActive(int64_t now) const { return ((now - _lastNontrivialReceive) < ZT_PEER_ACTIVITY_TIMEOUT); }
 
+	inline int64_t lastSentFullHello() { return _lastSentFullHello; }
+
 	/**
 	 * @return Latency in milliseconds of best/aggregate path or 0xffff if unknown / no paths
 	 */
diff --git a/service/OneService.cpp b/service/OneService.cpp
index daba4d99b..21717e3a5 100644
--- a/service/OneService.cpp
+++ b/service/OneService.cpp
@@ -2122,6 +2122,7 @@ public:
 			fprintf(stderr,"WARNING: using manually-specified secondary and/or tertiary ports. This can cause NAT issues." ZT_EOL_S);
 		}
 		_portMappingEnabled = OSUtils::jsonBool(settings["portMappingEnabled"],true);
+		_node->setLowBandwidthMode(OSUtils::jsonBool(settings["lowBandwidthMode"],false));
 
 #ifndef ZT_SDK
 		const std::string up(OSUtils::jsonString(settings["softwareUpdate"],ZT_SOFTWARE_UPDATE_DEFAULT));