diff --git a/src/config/Block.cxx b/src/config/Block.cxx
index bc451ad4d..107baa83f 100644
--- a/src/config/Block.cxx
+++ b/src/config/Block.cxx
@@ -143,3 +143,10 @@ ConfigBlock::GetBlockValue(const char *name, bool default_value) const
 
 	return bp->GetBoolValue();
 }
+
+void
+ConfigBlock::ThrowWithNested() const
+{
+	std::throw_with_nested(FormatRuntimeError("Error in block on line %i",
+						  line));
+}
diff --git a/src/config/Block.hxx b/src/config/Block.hxx
index 82fce9761..4f0428721 100644
--- a/src/config/Block.hxx
+++ b/src/config/Block.hxx
@@ -137,6 +137,14 @@ struct ConfigBlock {
 	unsigned GetPositiveValue(const char *name, unsigned default_value) const;
 
 	bool GetBlockValue(const char *name, bool default_value) const;
+
+	/**
+	 * Call this method in a "catch" block to throw a nested
+	 * exception showing the location of this block in the
+	 * configuration file.
+	 */
+	[[noreturn]]
+	void ThrowWithNested() const;
 };
 
 #endif
diff --git a/src/config/Data.hxx b/src/config/Data.hxx
index 73068f2c7..36bd3df6c 100644
--- a/src/config/Data.hxx
+++ b/src/config/Data.hxx
@@ -121,6 +121,26 @@ struct ConfigData {
 
 	ConfigBlock &MakeBlock(ConfigBlockOption option,
 				     const char *key, const char *value);
+
+	/**
+	 * Invoke the given function for each instance of the
+	 * specified block.
+	 *
+	 * Exceptions thrown by the function will be nested in one
+	 * that specifies the location of the block.
+	 */
+	template<typename F>
+	void WithEach(ConfigBlockOption option, F &&f) const {
+		for (const auto &block : GetBlockList(option)) {
+			block.SetUsed();
+
+			try {
+				f(block);
+			} catch (...) {
+				block.ThrowWithNested();
+			}
+		}
+	}
 };
 
 #endif
diff --git a/src/neighbor/Glue.cxx b/src/neighbor/Glue.cxx
index 6d3e8fa5d..183f030dd 100644
--- a/src/neighbor/Glue.cxx
+++ b/src/neighbor/Glue.cxx
@@ -48,24 +48,16 @@ void
 NeighborGlue::Init(const ConfigData &config,
 		   EventLoop &loop, NeighborListener &listener)
 {
-	for (const auto &block : config.GetBlockList(ConfigBlockOption::NEIGHBORS)) {
-		block.SetUsed();
+	config.WithEach(ConfigBlockOption::NEIGHBORS, [&, this](const auto &block){
+		const char *plugin_name = block.GetBlockValue("plugin");
+		if (plugin_name == nullptr)
+			throw std::runtime_error("Missing \"plugin\" configuration");
 
-		try {
-			const char *plugin_name = block.GetBlockValue("plugin");
-			if (plugin_name == nullptr)
-				throw std::runtime_error("Missing \"plugin\" configuration");
-
-			explorers.emplace_front(plugin_name,
-						CreateNeighborExplorer(loop,
-								       listener,
-								       plugin_name,
-								       block));
-		} catch (...) {
-			std::throw_with_nested(FormatRuntimeError("Line %i: ",
-								  block.line));
-		}
-	}
+		explorers.emplace_front(plugin_name,
+					CreateNeighborExplorer(loop, listener,
+							       plugin_name,
+							       block));
+	});
 }
 
 void
diff --git a/src/output/MultipleOutputs.cxx b/src/output/MultipleOutputs.cxx
index b728d06c4..e5ac932dc 100644
--- a/src/output/MultipleOutputs.cxx
+++ b/src/output/MultipleOutputs.cxx
@@ -92,8 +92,7 @@ MultipleOutputs::Configure(EventLoop &event_loop, EventLoop &rt_event_loop,
 	const AudioOutputDefaults defaults(config);
 	FilterFactory filter_factory(config);
 
-	for (const auto &block : config.GetBlockList(ConfigBlockOption::AUDIO_OUTPUT)) {
-		block.SetUsed();
+	config.WithEach(ConfigBlockOption::AUDIO_OUTPUT, [&, this](const auto &block){
 		auto output = LoadOutputControl(event_loop, rt_event_loop,
 						replay_gain_config,
 						mixer_listener,
@@ -104,7 +103,7 @@ MultipleOutputs::Configure(EventLoop &event_loop, EventLoop &rt_event_loop,
 						 "names: %s", output->GetName());
 
 		outputs.emplace_back(std::move(output));
-	}
+	});
 
 	if (outputs.empty()) {
 		/* auto-detect device */