diff --git a/include/libcamera/internal/global_configuration.h b/include/libcamera/internal/global_configuration.h
index 21bd50529..b9f6d134a 100644
--- a/include/libcamera/internal/global_configuration.h
+++ b/include/libcamera/internal/global_configuration.h
@@ -12,6 +12,8 @@
 #include <string>
 #include <string_view>
 
+#include <libcamera/base/utils.h>
+
 #include "libcamera/internal/yaml_parser.h"
 
 namespace libcamera {
@@ -25,11 +27,28 @@ public:
 
 	unsigned int version() const;
 	Configuration configuration() const;
-	std::optional<std::string> option(
+
+	template<typename T>
+	std::optional<T> option(
+		const std::initializer_list<std::string_view> confPath) const
+	{
+		const YamlObject *c = &configuration();
+		for (auto part : confPath) {
+			c = &(*c)[part];
+			if (!*c)
+				return {};
+		}
+		return c->get<T>();
+	}
+
+	std::optional<std::vector<std::string>> listOption(
 		const std::initializer_list<std::string_view> confPath) const;
 	std::optional<std::string> envOption(
 		const char *const envVariable,
 		const std::initializer_list<std::string_view> confPath) const;
+	std::optional<std::vector<std::string>> envListOption(
+		const char *const envVariable,
+		const std::initializer_list<std::string_view> confPath) const;
 
 private:
 	bool loadFile(const std::filesystem::path &fileName);
diff --git a/include/libcamera/internal/ipa_manager.h b/include/libcamera/internal/ipa_manager.h
index a0d448cf9..b0b44c74a 100644
--- a/include/libcamera/internal/ipa_manager.h
+++ b/include/libcamera/internal/ipa_manager.h
@@ -17,6 +17,7 @@
 #include <libcamera/ipa/ipa_module_info.h>
 
 #include "libcamera/internal/camera_manager.h"
+#include "libcamera/internal/global_configuration.h"
 #include "libcamera/internal/ipa_module.h"
 #include "libcamera/internal/pipeline_handler.h"
 #include "libcamera/internal/pub_key.h"
@@ -28,7 +29,7 @@ LOG_DECLARE_CATEGORY(IPAManager)
 class IPAManager
 {
 public:
-	IPAManager();
+	IPAManager(const GlobalConfiguration &configuration);
 	~IPAManager();
 
 	template<typename T>
@@ -42,7 +43,8 @@ public:
 		if (!m)
 			return nullptr;
 
-		std::unique_ptr<T> proxy = std::make_unique<T>(m, !self->isSignatureValid(m));
+		const GlobalConfiguration &configuration = cm->_d()->configuration();
+		std::unique_ptr<T> proxy = std::make_unique<T>(m, !self->isSignatureValid(m), configuration);
 		if (!proxy->isValid()) {
 			LOG(IPAManager, Error) << "Failed to load proxy";
 			return nullptr;
@@ -73,6 +75,7 @@ private:
 #if HAVE_IPA_PUBKEY
 	static const uint8_t publicKeyData_[];
 	static const PubKey pubKey_;
+	bool forceIsolation_;
 #endif
 };
 
diff --git a/include/libcamera/internal/ipa_proxy.h b/include/libcamera/internal/ipa_proxy.h
index 983bcc5fa..97c22c730 100644
--- a/include/libcamera/internal/ipa_proxy.h
+++ b/include/libcamera/internal/ipa_proxy.h
@@ -7,10 +7,14 @@
 
 #pragma once
 
+#include <optional>
 #include <string>
+#include <vector>
 
 #include <libcamera/ipa/ipa_interface.h>
 
+#include "libcamera/internal/global_configuration.h"
+
 namespace libcamera {
 
 class IPAModule;
@@ -24,7 +28,7 @@ public:
 		ProxyRunning,
 	};
 
-	IPAProxy(IPAModule *ipam);
+	IPAProxy(IPAModule *ipam, const GlobalConfiguration &configuration);
 	~IPAProxy();
 
 	bool isValid() const { return valid_; }
@@ -40,6 +44,8 @@ protected:
 
 private:
 	IPAModule *ipam_;
+	std::optional<std::vector<std::string>> configPaths_;
+	std::optional<std::vector<std::string>> execPaths_;
 };
 
 } /* namespace libcamera */
diff --git a/src/libcamera/camera_manager.cpp b/src/libcamera/camera_manager.cpp
index f3b4ec708..c47fd3677 100644
--- a/src/libcamera/camera_manager.cpp
+++ b/src/libcamera/camera_manager.cpp
@@ -41,7 +41,7 @@ LOG_DEFINE_CATEGORY(Camera)
 CameraManager::Private::Private()
 	: initialized_(false)
 {
-	ipaManager_ = std::make_unique<IPAManager>();
+	ipaManager_ = std::make_unique<IPAManager>(this->configuration());
 }
 
 int CameraManager::Private::start()
diff --git a/src/libcamera/global_configuration.cpp b/src/libcamera/global_configuration.cpp
index f36221e4d..cb01eff22 100644
--- a/src/libcamera/global_configuration.cpp
+++ b/src/libcamera/global_configuration.cpp
@@ -9,9 +9,11 @@
 
 #include <filesystem>
 #include <memory>
+#include <optional>
 #include <string>
 #include <string_view>
 #include <sys/types.h>
+#include <vector>
 
 #include <libcamera/base/file.h>
 #include <libcamera/base/log.h>
@@ -116,13 +118,22 @@ GlobalConfiguration::GlobalConfiguration()
  */
 
 /**
+ * \fn std::optional<T> GlobalConfiguration::option(const std::initializer_list<std::string_view> &confPath) const
  * \brief Return value of the configuration option identified by \a confPath
  * \param[in] confPath Sequence of the YAML section names (excluding
  * `configuration') leading to the requested option
  * \return A value if an item corresponding to \a confPath exists in the
  * configuration file, no value otherwise
  */
-std::optional<std::string> GlobalConfiguration::option(
+
+/**
+ * \brief Return values of the configuration option identified by \a confPath
+ * \tparam T The type of the retrieved configuration value
+ * \param[in] confPath Sequence of the YAML section names (excluding
+ * `configuration') leading to the requested list option, separated by dots
+ * \return A vector of strings or no value if not found
+ */
+std::optional<std::vector<std::string>> GlobalConfiguration::listOption(
 	const std::initializer_list<std::string_view> confPath) const
 {
 	const YamlObject *c = &configuration();
@@ -131,7 +142,7 @@ std::optional<std::string> GlobalConfiguration::option(
 		if (!*c)
 			return {};
 	}
-	return c->get<std::string>();
+	return c->getList<std::string>();
 }
 
 /**
@@ -158,7 +169,37 @@ std::optional<std::string> GlobalConfiguration::envOption(
 	const char *envValue = utils::secure_getenv(envVariable);
 	if (envValue)
 		return std::optional{ std::string{ envValue } };
-	return option(confPath);
+	return option<std::string>(confPath);
+}
+
+/**
+ * \brief Return values of the configuration option from a file or environment
+ * \param[in] envVariable Environment variable to get the value from
+ * \param[in] confPath The same as in GlobalConfiguration::option
+ *
+ * This helper looks first at the given environment variable and if it is
+ * defined (even if it is empty) then it splits its value by semicolons and
+ * returns the resulting list of strings. Otherwise it looks for \a confPath the
+ * same way as in GlobalConfiguration::option, value of which must be a list of
+ * strings.
+ *
+ * \note Support for using environment variables to configure libcamera behavior
+ * is provided here mostly for backward compatibility reasons. Introducing new
+ * configuration environment variables is discouraged.
+ *
+ * \return A vector of strings retrieved from the given environment option or
+ * configuration file or no value if not found; the vector may be empty
+ */
+std::optional<std::vector<std::string>> GlobalConfiguration::envListOption(
+	const char *const envVariable,
+	const std::initializer_list<std::string_view> confPath) const
+{
+	const char *envValue = utils::secure_getenv(envVariable);
+	if (envValue) {
+		auto items = utils::split(envValue, ":");
+		return std::vector<std::string>(items.begin(), items.end());
+	}
+	return listOption(confPath);
 }
 
 /**
diff --git a/src/libcamera/ipa_manager.cpp b/src/libcamera/ipa_manager.cpp
index 830750dcc..000d56efa 100644
--- a/src/libcamera/ipa_manager.cpp
+++ b/src/libcamera/ipa_manager.cpp
@@ -9,13 +9,17 @@
 
 #include <algorithm>
 #include <dirent.h>
+#include <numeric>
 #include <string.h>
+#include <string>
 #include <sys/types.h>
+#include <vector>
 
 #include <libcamera/base/file.h>
 #include <libcamera/base/log.h>
 #include <libcamera/base/utils.h>
 
+#include "libcamera/internal/global_configuration.h"
 #include "libcamera/internal/ipa_module.h"
 #include "libcamera/internal/ipa_proxy.h"
 #include "libcamera/internal/pipeline_handler.h"
@@ -101,30 +105,36 @@ LOG_DEFINE_CATEGORY(IPAManager)
  * The IPAManager class is meant to only be instantiated once, by the
  * CameraManager.
  */
-IPAManager::IPAManager()
+IPAManager::IPAManager(const GlobalConfiguration &configuration)
 {
 #if HAVE_IPA_PUBKEY
 	if (!pubKey_.isValid())
 		LOG(IPAManager, Warning) << "Public key not valid";
+
+	char *force = utils::secure_getenv("LIBCAMERA_IPA_FORCE_ISOLATION");
+	forceIsolation_ = (force && force[0] != '\0') ||
+			  (!force && configuration.option<bool>({ "ipa", "force_isolation" })
+					     .value_or(false));
 #endif
 
 	unsigned int ipaCount = 0;
 
 	/* User-specified paths take precedence. */
-	const char *modulePaths = utils::secure_getenv("LIBCAMERA_IPA_MODULE_PATH");
-	if (modulePaths) {
-		for (const auto &dir : utils::split(modulePaths, ":")) {
-			if (dir.empty())
-				continue;
-
-			ipaCount += addDir(dir.c_str());
-		}
+	const auto modulePaths =
+		configuration.envListOption(
+			"LIBCAMERA_IPA_MODULE_PATH", { "ipa", "module_paths" });
+	for (const auto &dir : modulePaths.value_or(std::vector<std::string>())) {
+		if (dir.empty())
+			continue;
 
-		if (!ipaCount)
-			LOG(IPAManager, Warning)
-				<< "No IPA found in '" << modulePaths << "'";
+		ipaCount += addDir(dir.c_str());
 	}
 
+	if (!ipaCount)
+		LOG(IPAManager, Warning) << "No IPA found in '"
+					 << utils::join(modulePaths.value_or(std::vector<std::string>()), ":")
+					 << "'";
+
 	/*
 	 * When libcamera is used before it is installed, load IPAs from the
 	 * same build directory as the libcamera library itself.
@@ -279,11 +289,10 @@ IPAModule *IPAManager::module(PipelineHandler *pipe, uint32_t minVersion,
 bool IPAManager::isSignatureValid([[maybe_unused]] IPAModule *ipa) const
 {
 #if HAVE_IPA_PUBKEY
-	char *force = utils::secure_getenv("LIBCAMERA_IPA_FORCE_ISOLATION");
-	if (force && force[0] != '\0') {
+	if (forceIsolation_) {
 		LOG(IPAManager, Debug)
 			<< "Isolation of IPA module " << ipa->path()
-			<< " forced through environment variable";
+			<< " forced through configuration";
 		return false;
 	}
 
diff --git a/src/libcamera/ipa_proxy.cpp b/src/libcamera/ipa_proxy.cpp
index 9907b9615..468e0ddf8 100644
--- a/src/libcamera/ipa_proxy.cpp
+++ b/src/libcamera/ipa_proxy.cpp
@@ -7,13 +7,16 @@
 
 #include "libcamera/internal/ipa_proxy.h"
 
+#include <string>
 #include <sys/stat.h>
 #include <sys/types.h>
 #include <unistd.h>
+#include <vector>
 
 #include <libcamera/base/log.h>
 #include <libcamera/base/utils.h>
 
+#include "libcamera/internal/global_configuration.h"
 #include "libcamera/internal/ipa_module.h"
 
 /**
@@ -48,10 +51,15 @@ LOG_DEFINE_CATEGORY(IPAProxy)
 /**
  * \brief Construct an IPAProxy instance
  * \param[in] ipam The IPA module
+ * \param[in] configuration The global configuration
  */
-IPAProxy::IPAProxy(IPAModule *ipam)
+IPAProxy::IPAProxy(IPAModule *ipam, const GlobalConfiguration &configuration)
 	: valid_(false), state_(ProxyStopped), ipam_(ipam)
 {
+	configPaths_ = configuration.envListOption("LIBCAMERA_IPA_CONFIG_PATH",
+						   { "ipa", "config_paths" });
+	execPaths_ = configuration.envListOption(
+		"LIBCAMERA_IPA_PROXY_PATH", { "ipa", "proxy_paths" });
 }
 
 IPAProxy::~IPAProxy()
@@ -122,20 +130,15 @@ std::string IPAProxy::configurationFile(const std::string &name,
 	int ret;
 
 	/*
-	 * Check the directory pointed to by the IPA config path environment
-	 * variable next.
+	 * Check the directory pointed to by the IPA config path next.
 	 */
-	const char *confPaths = utils::secure_getenv("LIBCAMERA_IPA_CONFIG_PATH");
-	if (confPaths) {
-		for (const auto &dir : utils::split(confPaths, ":")) {
-			if (dir.empty())
-				continue;
-
-			std::string confPath = dir + "/" + ipaName + "/" + name;
-			ret = stat(confPath.c_str(), &statbuf);
-			if (ret == 0 && (statbuf.st_mode & S_IFMT) == S_IFREG)
-				return confPath;
-		}
+	for (const auto &dir : configPaths_.value_or(std::vector<std::string>())) {
+		if (dir.empty())
+			continue;
+		std::string confPath = dir + "/" + ipaName + "/" + name;
+		ret = stat(confPath.c_str(), &statbuf);
+		if (ret == 0 && (statbuf.st_mode & S_IFMT) == S_IFREG)
+			return confPath;
 	}
 
 	std::string root = utils::libcameraSourcePath();
@@ -199,18 +202,14 @@ std::string IPAProxy::resolvePath(const std::string &file) const
 {
 	std::string proxyFile = "/" + file;
 
-	/* Check env variable first. */
-	const char *execPaths = utils::secure_getenv("LIBCAMERA_IPA_PROXY_PATH");
-	if (execPaths) {
-		for (const auto &dir : utils::split(execPaths, ":")) {
-			if (dir.empty())
-				continue;
-
-			std::string proxyPath = dir;
-			proxyPath += proxyFile;
-			if (!access(proxyPath.c_str(), X_OK))
-				return proxyPath;
-		}
+	/* Check the configuration first. */
+	for (const auto &dir : execPaths_.value_or(std::vector<std::string>())) {
+		if (dir.empty())
+			continue;
+
+		std::string proxyPath = dir + proxyFile;
+		if (!access(proxyPath.c_str(), X_OK))
+			return proxyPath;
 	}
 
 	/*
diff --git a/utils/codegen/ipc/generators/libcamera_templates/module_ipa_proxy.cpp.tmpl b/utils/codegen/ipc/generators/libcamera_templates/module_ipa_proxy.cpp.tmpl
index 9a3aadbd2..0f87e7976 100644
--- a/utils/codegen/ipc/generators/libcamera_templates/module_ipa_proxy.cpp.tmpl
+++ b/utils/codegen/ipc/generators/libcamera_templates/module_ipa_proxy.cpp.tmpl
@@ -45,8 +45,8 @@ namespace {{ns}} {
 {% endfor %}
 {%- endif %}
 
-{{proxy_name}}::{{proxy_name}}(IPAModule *ipam, bool isolate)
-	: IPAProxy(ipam), isolate_(isolate),
+{{proxy_name}}::{{proxy_name}}(IPAModule *ipam, bool isolate, const GlobalConfiguration &configuration)
+	: IPAProxy(ipam, configuration), isolate_(isolate),
 	  controlSerializer_(ControlSerializer::Role::Proxy), seq_(0)
 {
 	LOG(IPAProxy, Debug)
diff --git a/utils/codegen/ipc/generators/libcamera_templates/module_ipa_proxy.h.tmpl b/utils/codegen/ipc/generators/libcamera_templates/module_ipa_proxy.h.tmpl
index a0312a7c1..057c3ab03 100644
--- a/utils/codegen/ipc/generators/libcamera_templates/module_ipa_proxy.h.tmpl
+++ b/utils/codegen/ipc/generators/libcamera_templates/module_ipa_proxy.h.tmpl
@@ -37,7 +37,7 @@ namespace {{ns}} {
 class {{proxy_name}} : public IPAProxy, public {{interface_name}}, public Object
 {
 public:
-	{{proxy_name}}(IPAModule *ipam, bool isolate);
+	{{proxy_name}}(IPAModule *ipam, bool isolate, const GlobalConfiguration &configuration);
 	~{{proxy_name}}();
 
 {% for method in interface_main.methods %}
