diff --git a/libi2pd/Destination.cpp b/libi2pd/Destination.cpp
index 33eff029..b191cbf1 100644
--- a/libi2pd/Destination.cpp
+++ b/libi2pd/Destination.cpp
@@ -169,6 +169,46 @@ namespace client
 			return false;
 	}
 
+	bool LeaseSetDestination::Reconfigure(std::map<std::string, std::string> params)
+	{
+		
+		auto itr = params.find("i2cp.dontPublishLeaseSet");
+		if (itr != params.end())
+		{
+			m_IsPublic = itr->second != "true";
+		}
+		
+		int inLen, outLen, inQuant, outQuant, numTags, minLatency, maxLatency;
+		std::map<std::string, int&> intOpts = {
+			{I2CP_PARAM_INBOUND_TUNNEL_LENGTH, inLen},
+			{I2CP_PARAM_OUTBOUND_TUNNEL_LENGTH, outLen},
+			{I2CP_PARAM_INBOUND_TUNNELS_QUANTITY, inQuant},
+			{I2CP_PARAM_OUTBOUND_TUNNELS_QUANTITY, outQuant},
+			{I2CP_PARAM_TAGS_TO_SEND, numTags},
+			{I2CP_PARAM_MIN_TUNNEL_LATENCY, minLatency},
+			{I2CP_PARAM_MAX_TUNNEL_LATENCY, maxLatency}
+		};
+
+		auto pool = GetTunnelPool();
+		inLen = pool->GetNumInboundHops();
+		outLen = pool->GetNumOutboundHops();
+		inQuant = pool->GetNumInboundTunnels();
+		outQuant = pool->GetNumOutboundTunnels();
+		minLatency = 0;
+		maxLatency = 0;
+		
+		for (auto & opt : intOpts)
+		{
+			itr = params.find(opt.first);
+			if(itr != params.end())
+			{
+				opt.second = std::stoi(itr->second);
+			}
+		}
+		pool->RequireLatency(minLatency, maxLatency);
+		return pool->Reconfigure(inLen, outLen, inQuant, outQuant);
+	}
+	
 	std::shared_ptr<const i2p::data::LeaseSet> LeaseSetDestination::FindLeaseSet (const i2p::data::IdentHash& ident)
 	{
 		std::shared_ptr<i2p::data::LeaseSet> remoteLS;
diff --git a/libi2pd/Destination.h b/libi2pd/Destination.h
index 6f37e768..3f261bc9 100644
--- a/libi2pd/Destination.h
+++ b/libi2pd/Destination.h
@@ -96,6 +96,10 @@ namespace client
 
 			virtual bool Start ();
 			virtual bool Stop ();
+
+			/** i2cp reconfigure */
+			virtual bool Reconfigure(std::map<std::string, std::string> i2cpOpts);
+		
 			bool IsRunning () const { return m_IsRunning; };
 			boost::asio::io_service& GetService () { return m_Service; };
 			std::shared_ptr<i2p::tunnel::TunnelPool> GetTunnelPool () { return m_Pool; };
diff --git a/libi2pd/Tunnel.cpp b/libi2pd/Tunnel.cpp
index e2c12b83..73abc1a7 100644
--- a/libi2pd/Tunnel.cpp
+++ b/libi2pd/Tunnel.cpp
@@ -670,10 +670,13 @@ namespace tunnel
 					{
 						if (!tunnel->IsRecreated () && ts + TUNNEL_RECREATION_THRESHOLD > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT)
 						{
-							tunnel->SetIsRecreated ();
 							auto pool = tunnel->GetTunnelPool ();
-							if (pool)
+							// let it die if the tunnel pool has been reconfigured and this is old
+							if (pool && tunnel->GetTunnelConfig()->GetNumHops() == pool->GetNumOutboundHops())
+							{
+								tunnel->SetIsRecreated ();
 								pool->RecreateOutboundTunnel (tunnel);
+							}
 						}
 						if (ts + TUNNEL_EXPIRATION_THRESHOLD > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT)
 							tunnel->SetState (eTunnelStateExpiring);
@@ -721,10 +724,13 @@ namespace tunnel
 					{
 						if (!tunnel->IsRecreated () && ts + TUNNEL_RECREATION_THRESHOLD > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT)
 						{
-							tunnel->SetIsRecreated ();
 							auto pool = tunnel->GetTunnelPool ();
-							if (pool)
+							// let it die if the tunnel pool was reconfigured and has different number of hops
+							if (pool && tunnel->GetTunnelConfig()->GetNumHops() == pool->GetNumInboundHops())
+							{
+								tunnel->SetIsRecreated ();
 								pool->RecreateInboundTunnel (tunnel);
+							}
 						}
 
 						if (ts + TUNNEL_EXPIRATION_THRESHOLD > tunnel->GetCreationTime () + TUNNEL_EXPIRATION_TIMEOUT)
diff --git a/libi2pd/TunnelPool.cpp b/libi2pd/TunnelPool.cpp
index 52736fa0..4f740a09 100644
--- a/libi2pd/TunnelPool.cpp
+++ b/libi2pd/TunnelPool.cpp
@@ -69,6 +69,18 @@ namespace tunnel
 		m_Tests.clear ();
 	}
 
+	bool TunnelPool::Reconfigure(int inHops, int outHops, int inQuant, int outQuant)	{
+		if( inHops >= 0 && outHops >= 0 && inQuant > 0 && outQuant > 0)
+		{
+			m_NumInboundHops = inHops;
+			m_NumOutboundHops = outHops;
+			m_NumInboundTunnels = inQuant;
+			m_NumOutboundTunnels = outQuant;
+			return true;
+		}
+		return false;
+	}
+	
 	void TunnelPool::TunnelCreated (std::shared_ptr<InboundTunnel> createdTunnel)
 	{
 		if (!m_IsActive) return;
@@ -479,11 +491,17 @@ namespace tunnel
 			outboundTunnel = tunnels.GetNextOutboundTunnel ();
 		LogPrint (eLogDebug, "Tunnels: Re-creating destination inbound tunnel...");
 		std::shared_ptr<TunnelConfig> config;
-		if (m_NumInboundHops > 0) config = std::make_shared<TunnelConfig>(tunnel->GetPeers ());
-		auto newTunnel = tunnels.CreateInboundTunnel (config, outboundTunnel);
-		newTunnel->SetTunnelPool (shared_from_this());
-		if (newTunnel->IsEstablished ()) // zero hops
-			TunnelCreated (newTunnel);
+		if (m_NumInboundHops > 0 && tunnel->GetPeers().size())
+		{
+			config = std::make_shared<TunnelConfig>(tunnel->GetPeers ());
+		}
+		if (m_NumInboundHops == 0 || config)
+		{
+			auto newTunnel = tunnels.CreateInboundTunnel (config, outboundTunnel);
+			newTunnel->SetTunnelPool (shared_from_this());
+			if (newTunnel->IsEstablished ()) // zero hops
+				TunnelCreated (newTunnel);
+		}
 	}
 
 	void TunnelPool::CreateOutboundTunnel ()
@@ -521,12 +539,17 @@ namespace tunnel
 		{
 			LogPrint (eLogDebug, "Tunnels: Re-creating destination outbound tunnel...");
 			std::shared_ptr<TunnelConfig> config;
-			if (m_NumOutboundHops > 0)
+			if (m_NumOutboundHops > 0 && tunnel->GetPeers().size())
+			{
 				config = std::make_shared<TunnelConfig>(tunnel->GetPeers (), inboundTunnel->GetNextTunnelID (), inboundTunnel->GetNextIdentHash ());
-			auto newTunnel = tunnels.CreateOutboundTunnel (config);
-			newTunnel->SetTunnelPool (shared_from_this ());
-			if (newTunnel->IsEstablished ()) // zero hops
-				TunnelCreated (newTunnel);
+			}
+			if(m_NumOutboundHops == 0 || config)
+			{
+				auto newTunnel = tunnels.CreateOutboundTunnel (config);
+				newTunnel->SetTunnelPool (shared_from_this ());
+				if (newTunnel->IsEstablished ()) // zero hops
+					TunnelCreated (newTunnel);
+			}
 		}
 		else
 			LogPrint (eLogDebug, "Tunnels: Can't re-create outbound tunnel, no inbound tunnels found");
diff --git a/libi2pd/TunnelPool.h b/libi2pd/TunnelPool.h
index 07c3024e..fc46930c 100644
--- a/libi2pd/TunnelPool.h
+++ b/libi2pd/TunnelPool.h
@@ -78,7 +78,12 @@ namespace tunnel
 
 			int GetNumInboundTunnels () const { return m_NumInboundTunnels; };
 			int GetNumOutboundTunnels () const { return m_NumOutboundTunnels; };
+			int GetNumInboundHops() const { return m_NumInboundHops; };
+			int GetNumOutboundHops() const { return m_NumOutboundHops; };
 
+			/** i2cp reconfigure */
+			bool Reconfigure(int inboundHops, int outboundHops, int inboundQuant, int outboundQuant);
+    
 			void SetCustomPeerSelector(ITunnelPeerSelector * selector);
 			void UnsetCustomPeerSelector();
 			bool HasCustomPeerSelector();
diff --git a/libi2pd_client/I2CP.cpp b/libi2pd_client/I2CP.cpp
index 371456ee..361d9f94 100644
--- a/libi2pd_client/I2CP.cpp
+++ b/libi2pd_client/I2CP.cpp
@@ -416,9 +416,60 @@ namespace client
 
 	void I2CPSession::ReconfigureSessionMessageHandler (const uint8_t * buf, size_t len)
 	{
-		// TODO: implement actual reconfiguration
-		SendSessionStatusMessage (2); // updated
-	}
+		uint8_t status = 3; // rejected
+		if(len > sizeof(uint16_t))
+		{
+			uint16_t sessionID = bufbe16toh(buf);
+			if(sessionID == m_SessionID)
+			{
+				buf += sizeof(uint16_t);
+				const uint8_t * body = buf;
+				i2p::data::IdentityEx ident;
+				if(ident.FromBuffer(buf, len - sizeof(uint16_t)))
+				{
+					if (ident == *m_Destination->GetIdentity())
+					{
+						size_t identsz = ident.GetFullLen();
+						buf += identsz;
+						uint16_t optssize = bufbe16toh(buf);
+						if (optssize <= len - sizeof(uint16_t) - sizeof(uint64_t) - identsz - ident.GetSignatureLen() - sizeof(uint16_t))
+						{
+							buf += sizeof(uint16_t);
+							std::map<std::string, std::string> opts;
+							ExtractMapping(buf, optssize, opts);
+							buf += optssize;
+							//uint64_t date = bufbe64toh(buf);
+							buf += sizeof(uint64_t);
+							const uint8_t * sig = buf;
+							if(ident.Verify(body, len - sizeof(uint16_t) - ident.GetSignatureLen(), sig))
+							{
+								if(m_Destination->Reconfigure(opts))
+								{
+									LogPrint(eLogInfo, "I2CP: reconfigured destination");
+									status = 2; // updated
+								}
+								else
+									LogPrint(eLogWarning, "I2CP: failed to reconfigure destination");
+							}
+							else
+								LogPrint(eLogError, "I2CP: invalid reconfigure message signature");
+						}
+						else
+							LogPrint(eLogError, "I2CP: mapping size missmatch");
+					}
+					else
+						LogPrint(eLogError, "I2CP: destination missmatch");
+				}
+				else
+					LogPrint(eLogError, "I2CP: malfromed destination");
+			}
+			else
+				LogPrint(eLogError, "I2CP: session missmatch");
+		}
+		else
+			LogPrint(eLogError, "I2CP: short message");
+		SendSessionStatusMessage (status); 
+	}	
 
 	void I2CPSession::SendSessionStatusMessage (uint8_t status)
 	{