diff --git a/NEWS b/NEWS
index 4a146652bde5dc82ae0d4aee7046d330506d3e80..86fe35980d1995d4a6b319d58610b3b4e9f894f8 100644
--- a/NEWS
+++ b/NEWS
@@ -15,6 +15,8 @@ Improvements
 - policy: recognize 100.64.0.0/10 as local addresses
 - layer/iterate: *do* retry repeatedly if REFUSED, as we can't yet easily
   retry with other NSs while avoiding retrying with those who REFUSED
+- modules: allow changing the directory where modules are found,
+  and do not search the default library path anymore.
 
 
 Knot Resolver 1.2.6 (2017-04-24)
diff --git a/daemon/README.rst b/daemon/README.rst
index 26301b9fc37b44948063c4ff03ed252317eab861..ba01fb86404383500229036161a55ad2b1fb83f3 100644
--- a/daemon/README.rst
+++ b/daemon/README.rst
@@ -380,7 +380,15 @@ Environment
    hostname. If called without a parameter, it will return kresd's
    internal hostname, or the system's POSIX hostname (see
    gethostname(2)) if kresd's internal hostname is unset.
-	    
+
+.. function:: moduledir([dir])
+
+   :return: Modules directory.
+
+   If called with a parameter, it will change kresd's directory for
+   looking up the dynamic modules.  If called without a parameter, it
+   will return kresd's modules directory.
+   
 .. function:: verbose(true | false)
 
    :return: Toggle verbose logging.
diff --git a/daemon/daemon.mk b/daemon/daemon.mk
index ac692f7754228d19459bcaa8481fa067f78572bb..ed5a10288a833c584d29e9d90b0299700ea62410 100644
--- a/daemon/daemon.mk
+++ b/daemon/daemon.mk
@@ -51,7 +51,9 @@ date := $(shell date +%F -r NEWS)
 daemon: $(kresd) $(kresd_DIST)
 daemon-install: kresd-install bindings-install
 ifneq ($(SED),)
-	$(SED) -e "s/@VERSION@/$(VERSION)/" -e "s/@DATE@/$(date)/" doc/kresd.8.in > doc/kresd.8
+	$(SED) -e "s/@VERSION@/$(VERSION)/" -e "s/@DATE@/$(date)/" \
+		-e "s|@MODULEDIR@|$(MODULEDIR)|" \
+		doc/kresd.8.in > doc/kresd.8
 	$(INSTALL) -d -m 0755 $(DESTDIR)$(MANDIR)/man8/
 	$(INSTALL) -m 0644 doc/kresd.8 $(DESTDIR)$(MANDIR)/man8/
 endif
diff --git a/daemon/engine.c b/daemon/engine.c
index 520cd3cd32a9ff038b75e9f8b8ebefa1b2e7904f..08b02c647acaf9a8abdc794450c34de266a775c1 100644
--- a/daemon/engine.c
+++ b/daemon/engine.c
@@ -21,6 +21,7 @@
 #include <unistd.h>
 #include <grp.h>
 #include <pwd.h>
+#include <sys/param.h>
 #include <zscanner/scanner.h>
 
 #include "daemon/engine.h"
@@ -43,6 +44,13 @@ KR_NORETURN int lua_error (lua_State *L);
 /* Cleanup engine state every 5 minutes */
 const size_t CLEANUP_TIMER = 5*60*1000;
 
+/* Execute byte code */
+#define l_dobytecode(L, arr, len, name) \
+	(luaL_loadbuffer((L), (arr), (len), (name)) || lua_pcall((L), 0, LUA_MULTRET, 0))
+/** Load file in a sandbox environment. */
+#define l_dosandboxfile(L, filename) \
+	(luaL_loadfile((L), (filename)) || engine_pcall((L), 0))
+
 /*
  * Global bindings.
  */
@@ -201,6 +209,63 @@ static int l_hostname(lua_State *L)
 	return 1;
 }
 
+char *engine_get_moduledir(struct engine *engine) {
+	return engine->moduledir;
+}
+
+int engine_set_moduledir(struct engine *engine, const char *moduledir) {
+	if (!engine || !moduledir) {
+		return kr_error(EINVAL);
+	}
+
+	char *new_moduledir = strdup(moduledir);
+	if (!new_moduledir) {
+		return kr_error(ENOMEM);
+	}
+	if (engine->moduledir) {
+		free(engine->moduledir);
+	}
+	engine->moduledir = new_moduledir;
+
+	/* Use module path for including Lua scripts */
+	char l_paths[MAXPATHLEN] = { 0 };
+	/* Save original package.path to package._path */
+	snprintf(l_paths, MAXPATHLEN - 1,
+		 "if package._path == nil then package._path = package.path end\n"
+		 "package.path = '%s/?.lua;%s/?/init.lua;'..package._path\n",
+		 new_moduledir,
+		 new_moduledir);
+
+	int ret = l_dobytecode(engine->L, l_paths, strlen(l_paths), "");
+	if (ret != 0) {
+		lua_pop(engine->L, 1);
+		return ret;
+	}
+	return 0;
+}
+
+/** Return hostname. */
+static int l_moduledir(lua_State *L)
+{
+	struct engine *engine = engine_luaget(L);
+	if (lua_gettop(L) == 0) {
+		lua_pushstring(L, engine_get_moduledir(engine));
+		return 1;
+	}
+	if ((lua_gettop(L) != 1) || !lua_isstring(L, 1)) {
+		lua_pushstring(L, "moduledir takes at most one parameter: (\"directory\")");
+		lua_error(L);
+	}
+
+	if (engine_set_moduledir(engine, lua_tostring(L, 1)) != 0) {
+		lua_pushstring(L, "setting moduledir failed");
+		lua_error(L);
+	}
+
+	lua_pushstring(L, engine_get_moduledir(engine));
+	return 1;
+}
+
 /** Get/set context option. */
 static int l_option(lua_State *L)
 {
@@ -532,6 +597,8 @@ static int init_state(struct engine *engine)
 	lua_setglobal(engine->L, "quit");
 	lua_pushcfunction(engine->L, l_hostname);
 	lua_setglobal(engine->L, "hostname");
+	lua_pushcfunction(engine->L, l_moduledir);
+	lua_setglobal(engine->L, "moduledir");
 	lua_pushcfunction(engine->L, l_verbose);
 	lua_setglobal(engine->L, "verbose");
 	lua_pushcfunction(engine->L, l_option);
@@ -548,8 +615,6 @@ static int init_state(struct engine *engine)
 	lua_setglobal(engine->L, "tojson");
 	lua_pushcfunction(engine->L, l_map);
 	lua_setglobal(engine->L, "map");
-	lua_pushliteral(engine->L, MODULEDIR);
-	lua_setglobal(engine->L, "moduledir");
 	lua_pushlightuserdata(engine->L, engine);
 	lua_setglobal(engine->L, "__engine");
 	return kr_ok();
@@ -643,6 +708,8 @@ void engine_deinit(struct engine *engine)
 	array_clear(engine->ipc_set);
 	kr_ta_clear(&engine->resolver.trust_anchors);
 	kr_ta_clear(&engine->resolver.negative_anchors);
+	free(engine->hostname);
+	free(engine->moduledir);
 }
 
 int engine_pcall(lua_State *L, int argc)
@@ -685,21 +752,9 @@ int engine_ipc(struct engine *engine, const char *expr)
 	}
 }
 
-/* Execute byte code */
-#define l_dobytecode(L, arr, len, name) \
-	(luaL_loadbuffer((L), (arr), (len), (name)) || lua_pcall((L), 0, LUA_MULTRET, 0))
-/** Load file in a sandbox environment. */
-#define l_dosandboxfile(L, filename) \
-	(luaL_loadfile((L), (filename)) || engine_pcall((L), 0))
-
 static int engine_loadconf(struct engine *engine, const char *config_path)
 {
-	/* Use module path for including Lua scripts */
-	static const char l_paths[] = "package.path = '" MODULEDIR "/?.lua;'..package.path";
-	int ret = l_dobytecode(engine->L, l_paths, sizeof(l_paths) - 1, "");
-	if (ret != 0) {
-		lua_pop(engine->L, 1);
-	}
+	int ret = 0;
 	/* Init environment */
 	static const char sandbox_bytecode[] = {
 		#include "daemon/lua/sandbox.inc"
@@ -836,7 +891,7 @@ int engine_register(struct engine *engine, const char *name, const char *precede
 		return kr_error(ENOMEM);
 	}
 	module->data = engine;
-	int ret = kr_module_load(module, name, NULL);
+	int ret = kr_module_load(module, name, engine->moduledir);
 	/* Load Lua module if not a binary */
 	if (ret == kr_error(ENOENT)) {
 		ret = ffimodule_register_lua(engine, module, name);
diff --git a/daemon/engine.h b/daemon/engine.h
index 409e40982329f283ebeb9053c38d0dcaf4a5560d..a4f6ab5f45d8f5f55f037c66e07b56e5561f02e9 100644
--- a/daemon/engine.h
+++ b/daemon/engine.h
@@ -61,6 +61,7 @@ struct engine {
     uv_timer_t *updater;
     char *hostname;
     struct lua_State *L;
+    char *moduledir;
 };
 
 int engine_init(struct engine *engine, knot_mm_t *pool);
@@ -89,3 +90,7 @@ struct engine *engine_luaget(struct lua_State *L);
 /** Set/get the per engine hostname */
 char *engine_get_hostname(struct engine *engine);
 int engine_set_hostname(struct engine *engine, const char *hostname);
+
+/** Set/get the per engine moduledir */
+char *engine_get_moduledir(struct engine *engine);
+int engine_set_moduledir(struct engine *engine, const char *moduledir);
diff --git a/daemon/main.c b/daemon/main.c
index 2ebbf6ad427104cda25383a6eef2b9804bcdd6ba..6de5e5cd3a32628326213b31c113af2343ccc08d 100644
--- a/daemon/main.c
+++ b/daemon/main.c
@@ -341,15 +341,16 @@ static void help(int argc, char *argv[])
 {
 	printf("Usage: %s [parameters] [rundir]\n", argv[0]);
 	printf("\nParameters:\n"
-	       " -a, --addr=[addr]    Server address (default: localhost@53).\n"
-	       " -t, --tls=[addr]     Server address for TLS (default: off).\n"
-	       " -S, --fd=[fd]        Listen on given fd (handed out by supervisor).\n"
-	       " -T, --tlsfd=[fd]     Listen using TLS on given fd (handed out by supervisor).\n"
-	       " -c, --config=[path]  Config file path (relative to [rundir]) (default: config).\n"
-	       " -k, --keyfile=[path] File containing trust anchors (DS or DNSKEY).\n"
-	       " -f, --forks=N        Start N forks sharing the configuration.\n"
-	       " -q, --quiet          Quiet output, no prompt in interactive mode.\n"
-	       " -v, --verbose        Run in verbose mode."
+	       " -a, --addr=[addr]      Server address (default: localhost@53).\n"
+	       " -t, --tls=[addr]       Server address for TLS (default: off).\n"
+	       " -S, --fd=[fd]          Listen on given fd (handed out by supervisor).\n"
+	       " -T, --tlsfd=[fd]       Listen using TLS on given fd (handed out by supervisor).\n"
+	       " -c, --config=[path]    Config file path (relative to [rundir]) (default: config).\n"
+	       " -k, --keyfile=[path]   File containing trust anchors (DS or DNSKEY).\n"
+	       " -m, --moduledir=[path] Override the default module path (" MODULEDIR ").\n"
+	       " -f, --forks=N          Start N forks sharing the configuration.\n"
+	       " -q, --quiet            Quiet output, no prompt in interactive mode.\n"
+	       " -v, --verbose          Run in verbose mode."
 #ifdef NOVERBOSELOG
 	           " (Recompile without -DNOVERBOSELOG to activate.)"
 #endif
@@ -433,6 +434,7 @@ int main(int argc, char **argv)
 	array_t(int) tls_fd_set;
 	array_init(tls_fd_set);
 	char *keyfile = NULL;
+	char *moduledir = MODULEDIR;
 	const char *config = NULL;
 	int control_fd = -1;
 
@@ -446,13 +448,14 @@ int main(int argc, char **argv)
 		{"config", required_argument, 0, 'c'},
 		{"keyfile",required_argument, 0, 'k'},
 		{"forks",required_argument,   0, 'f'},
+		{"moduledir", required_argument, 0, 'm'},
 		{"verbose",    no_argument,   0, 'v'},
 		{"quiet",      no_argument,   0, 'q'},
 		{"version",   no_argument,    0, 'V'},
 		{"help",      no_argument,    0, 'h'},
 		{0, 0, 0, 0}
 	};
-	while ((c = getopt_long(argc, argv, "a:t:S:T:c:f:k:vqVh", opts, &li)) != -1) {
+	while ((c = getopt_long(argc, argv, "a:t:S:T:c:f:m:k:vqVh", opts, &li)) != -1) {
 		switch (c)
 		{
 		case 'a':
@@ -482,6 +485,9 @@ int main(int argc, char **argv)
 		case 'k':
 			keyfile = optarg;
 			break;
+		case 'm':
+			moduledir = optarg;
+			break;
 		case 'v':
 			kr_verbose_set(true);
 #ifdef NOVERBOSELOG
@@ -636,6 +642,8 @@ int main(int argc, char **argv)
 		goto cleanup;
 	}
 
+	engine_set_moduledir(&engine, moduledir);
+	
 	/* Block signals. */
 	uv_loop_t *loop = uv_default_loop();
 	uv_signal_t sigint, sigterm;
diff --git a/doc/build.rst b/doc/build.rst
index 2b054b5dd012b30d788e81075c0f6dbb8f13718f..b27ddd7c910ab47bfd430a9cf0ee61e06e8f8caf 100644
--- a/doc/build.rst
+++ b/doc/build.rst
@@ -175,11 +175,11 @@ All paths are prefixed with ``PREFIX`` variable by default if not specified othe
    "library", "``LIBDIR``", "``$(PREFIX)/lib``", "pkg-config is auto-generated [#]_"
    "daemon",  "``SBINDIR``", "``$(PREFIX)/sbin``", ""
    "configuration", "``ETCDIR``", "``$(PREFIX)/etc/kresd``", "Configuration file, templates."
-   "modules", "``MODULEDIR``", "``$(LIBDIR)/kdns_modules``", "[#]_"
+   "modules", "``MODULEDIR``", "``$(LIBDIR)/kdns_modules``", "Runtime directory for loading dynamic modules [#]_."
    "work directory", "", "``$(PREFIX)/var/run/kresd``", "Run directory for daemon."
 
 .. [#] The ``libkres.pc`` is installed in ``$(LIBDIR)/pkgconfig``.
-.. [#] Users may install additional modules in ``~/.local/lib/kdns_modules`` or in the rundir of a specific instance.
+.. [#] The default moduledir can be changed with `-m` option to `kresd` daemon or by calling `moduledir()` function from lua.
 
 .. note:: Each module is self-contained and may install additional bundled files within ``$(MODULEDIR)/$(modulename)``. These files should be read-only, non-executable.
 
diff --git a/doc/kresd.8.in b/doc/kresd.8.in
index a54a2623299df99fb738177ff02731451b4242f0..a1be6e24e0d81e0adebaec7e497549fb4d529d90 100644
--- a/doc/kresd.8.in
+++ b/doc/kresd.8.in
@@ -14,12 +14,18 @@
 .B kresd
 .RB [ \-a | \-\-addr
 .IR addr[@port] ]
+.RB [ \-t | \-\-tls
+.IR addr[@port] ]
 .RB [ \-S | \-\-fd
 .IR fd ]
+.RB [ \-T | \-\-tlsfd
+.IR fd ]
 .RB [ \-c | \-\-config
 .IR config ]
 .RB [ \-k | \-\-keyfile
 .IR keyfile ]
+.RB [ \-m | \-\-moduledir
+.IR path ]
 .RB [ \-f | \-\-forks
 .IR N ]
 .RB [ \-q | \-\-quiet ]
@@ -109,12 +115,20 @@ Option may be passed multiple times to listen on more file descriptors.
 Listen using TLS on given file descriptor(s), passed by supervisor.
 Option may be passed multiple times to listen on more file descriptors.
 .TP
+.B \-c\fI config\fR, \fB\-\-config=\fI<config>
+Set the config file with settings for kresd to read instead of reading the
+file at the default location (\fIconfig\fR). The syntax is
+described in \fIdaemon/README.md\fR.
+.TP
 .B \-k\fI keyfile\fR, \fB\-\-keyfile=\fI<keyfile>
 Use given for keeping root trust anchors. If the file doesn't exist, it will be
 automatically boostrapped from IANA and warning for you will be issued to check it
 before trusting it. The file contains DNSKEY/DS records in presentation format,
 and is compatible with Unbound or BIND9 root key files.
 .TP
+.B \-m\fI path\fR, \fB\-\-moduledir=\fI<path>
+Override the directory that is searched for modules.  Default: @MODULEDIR@
+.TP
 .B \-f\fI N\fR, \fB\-\-forks=\fI<N>
 With this option, the daemon is started in non-interactive mode and instead creates a
 UNIX socket in \fIrundir\fR that the operator can connect to for interactive session.
@@ -132,13 +146,8 @@ Daemon will refrain from printing any informative messages, not even a prompt.
 Increase verbosity. If given multiple times, more information is logged.
 This is in addition to the verbosity (if any) from the config file.
 .TP
-.B \-c\fI config\fR, \fB\-\-config=\fI<config>
-Set the config file with settings for kresd to read instead of reading the
-file at the default location (\fIconfig\fR). The syntax is
-described in \fIdaemon/README.md\fR.
-.TP
 .B \-h
-Show the version and commandline option help.
+Show short commandline option help.
 .TP
 .B \-V
 Show the version.
diff --git a/lib/module.c b/lib/module.c
index 6cd46236580688aff7f319d81c7c1437d876bc09..67b6aff15c47c51816d017704b5c04e31392bfc8 100644
--- a/lib/module.c
+++ b/lib/module.c
@@ -54,13 +54,9 @@ static void *load_symbol(void *lib, const char *prefix, const char *name)
 
 static int load_library(struct kr_module *module, const char *name, const char *path)
 {
+	assert(module && name && path);
 	/* Absolute or relative path (then only library search path is used). */
-	auto_free char *lib_path = NULL;
-	if (path != NULL) {
-		lib_path = kr_strcatdup(4, path, "/", name, LIBEXT);
-	} else {
-		lib_path = kr_strcatdup(2, name, LIBEXT);
-	}
+	auto_free char *lib_path = kr_strcatdup(4, path, "/", name, LIBEXT);
 	if (lib_path == NULL) {
 		return kr_error(ENOMEM);
 	}
@@ -130,15 +126,9 @@ int kr_module_load(struct kr_module *module, const char *name, const char *path)
 		return kr_error(ENOMEM);
 	}
 
-	/* Search for module library, use current namespace if not found. */
-	if (load_library(module, name, path) != 0) {
-		/* Expand HOME env variable, as the linker may not expand it. */
-		auto_free char *local_path = kr_strcatdup(2, getenv("HOME"), "/.local/lib/kdns_modules");
-		if (load_library(module, name, local_path) != 0) {
-			if (load_library(module, name, MODULEDIR) != 0) {
-				module->lib = RTLD_DEFAULT;
-			}
-		}
+	/* Search for module library. */
+	if (!path || load_library(module, name, path) != 0) {
+		module->lib = RTLD_DEFAULT;
 	}
 
 	/* Try to load module ABI. */
diff --git a/lib/module.h b/lib/module.h
index 92ab3a1977834f4f2927cac0d7fcb7f831e411ad..29afc9884966136023c5c03a74af185cdbb09f18 100644
--- a/lib/module.h
+++ b/lib/module.h
@@ -84,7 +84,7 @@ struct kr_prop {
 
 
 /**
- * Load module instance into memory.
+ * Load a C module instance into memory.
  *
  * @param module module structure
  * @param name module name