diff --git a/package/gluon-web-admin/files/lib/gluon/config-mode/view/admin/info.html b/package/gluon-web-admin/files/lib/gluon/config-mode/view/admin/info.html
index 7e060f575f478f29e601aa0d8754faddbd0c6766..28ae88e6dfff16969f5f780cd7e2b7c3a827408a 100644
--- a/package/gluon-web-admin/files/lib/gluon/config-mode/view/admin/info.html
+++ b/package/gluon-web-admin/files/lib/gluon/config-mode/view/admin/info.html
@@ -1,5 +1,4 @@
 <%-
-	local fs = require 'nixio.fs'
 	local uci = require('simple-uci').cursor()
 	local pretty_hostname = require 'pretty_hostname'
 
@@ -24,8 +23,8 @@
 		{ _('Hostname'), pretty_hostname.get(uci) },
 		{ _('MAC address'), sysconfig.primary_mac },
 		{ _('Hardware model'), platform.get_model() },
-		{ _('Gluon version'), util.trim(fs.readfile('/lib/gluon/gluon-version')) },
-		{ _('Firmware release'), util.trim(fs.readfile('/lib/gluon/release')) },
+		{ _('Gluon version'), util.trim(util.readfile('/lib/gluon/gluon-version')) },
+		{ _('Firmware release'), util.trim(util.readfile('/lib/gluon/release')) },
 		{ _('Site'), site.site_name() },
 		{ _('Public VPN key'), pubkey },
 	}
diff --git a/package/gluon-web-admin/luasrc/lib/gluon/config-mode/controller/admin/upgrade.lua b/package/gluon-web-admin/luasrc/lib/gluon/config-mode/controller/admin/upgrade.lua
index 42f5be489db87c30e2da2c20773bb2d84e0b781f..a4531213c5e001395902e5ab199cf53be57fa67d 100644
--- a/package/gluon-web-admin/luasrc/lib/gluon/config-mode/controller/admin/upgrade.lua
+++ b/package/gluon-web-admin/luasrc/lib/gluon/config-mode/controller/admin/upgrade.lua
@@ -13,13 +13,13 @@ package 'gluon-web-admin'
 
 
 local util = require 'gluon.util'
-local fs = require 'nixio.fs'
+local unistd = require 'posix.unistd'
 
 local tmpfile = "/tmp/firmware.img"
 
 
 local function filehandler(meta, chunk, eof)
-	if not fs.access(tmpfile) and not file and chunk and #chunk > 0 then
+	if not unistd.access(tmpfile) and not file and chunk and #chunk > 0 then
 		file = io.open(tmpfile, "w")
 	end
 	if file and chunk then
@@ -31,33 +31,35 @@ local function filehandler(meta, chunk, eof)
 end
 
 local function action_upgrade(http, renderer)
-	local nixio = require 'nixio'
+	local fcntl = require 'posix.fcntl'
+	local stat = require 'posix.sys.stat'
+	local wait = require 'posix.sys.wait'
 
-	local function fork_exec(...)
-		local pid = nixio.fork()
+	local function fork_exec(argv)
+		local pid = unistd.fork()
 		if pid > 0 then
 			return
 		elseif pid == 0 then
 			-- change to root dir
-			nixio.chdir("/")
+			unistd.chdir('/')
 
 			-- patch stdin, out, err to /dev/null
-			local null = nixio.open("/dev/null", "w+")
+			local null = fcntl.open('/dev/null', fcntl.O_RDWR)
 			if null then
-				nixio.dup(null, nixio.stderr)
-				nixio.dup(null, nixio.stdout)
-				nixio.dup(null, nixio.stdin)
-				if null:fileno() > 2 then
-					null:close()
+				unistd.dup2(null, unistd.STDIN_FILENO)
+				unistd.dup2(null, unistd.STDOUT_FILENO)
+				unistd.dup2(null, unistd.STDERR_FILENO)
+				if null > 2 then
+					unistd.close(null)
 				end
 			end
 
 			-- Sleep a little so the browser can fetch everything required to
 			-- display the reboot page, then reboot the device.
-			nixio.nanosleep(1)
+			unistd.sleep(1)
 
 			-- replace with target command
-			nixio.exec(...)
+			unistd.exec(argv[0], argv)
 		end
 	end
 
@@ -67,7 +69,7 @@ local function action_upgrade(http, renderer)
 
 	local function storage_size()
 		local size = 0
-		if fs.access("/proc/mtd") then
+		if unistd.access("/proc/mtd") then
 			for l in io.lines("/proc/mtd") do
 				local d, s, e, n = l:match('^([^%s]+)%s+([^%s]+)%s+([^%s]+)%s+"([^%s]+)"')
 				if n == "linux" then
@@ -75,7 +77,7 @@ local function action_upgrade(http, renderer)
 					break
 				end
 			end
-		elseif fs.access("/proc/partitions") then
+		elseif unistd.access("/proc/partitions") then
 			for l in io.lines("/proc/partitions") do
 				local x, y, b, n = l:match('^%s*(%d+)%s+(%d+)%s+([^%s]+)%s+([^%s]+)')
 				if b and n and not n:match('[0-9]') then
@@ -95,7 +97,7 @@ local function action_upgrade(http, renderer)
 	-- Determine state
 	local step = tonumber(http:getenv("REQUEST_METHOD") == "POST" and http:formvalue("step")) or 1
 
-	local has_image   = fs.access(tmpfile)
+	local has_image   = unistd.access(tmpfile)
 	local has_support = has_image and image_supported(tmpfile)
 
 	-- Step 1: file upload, error on unsupported image format
@@ -103,7 +105,7 @@ local function action_upgrade(http, renderer)
 		-- If there is an image but user has requested step 1
 		-- or type is not supported, then remove it.
 		if has_image then
-			fs.unlink(tmpfile)
+			unistd.unlink(tmpfile)
 		end
 
 		renderer.render_layout('admin/upgrade', {
@@ -114,17 +116,17 @@ local function action_upgrade(http, renderer)
 	elseif step == 2 then
 		renderer.render_layout('admin/upgrade_confirm', {
 			checksum   = image_checksum(tmpfile),
-			filesize   = fs.stat(tmpfile).size,
+			filesize   = stat.stat(tmpfile).st_size,
 			flashsize  = storage_size(),
 			keepconfig = (http:formvalue("keepcfg") == "1"),
 		}, 'gluon-web-admin')
 
 	elseif step == 3 then
-		if http:formvalue("keepcfg") == "1" then
-			fork_exec("/sbin/sysupgrade", tmpfile)
-		else
-			fork_exec("/sbin/sysupgrade", "-n", tmpfile)
+		local cmd = {[0] = '/sbin/sysupgrade', tmpfile}
+		if http:formvalue('keepcfg') ~= '1' then
+			table.insert(cmd, 1, '-n')
 		end
+		fork_exec(cmd)
 		renderer.render_layout('admin/upgrade_reboot', nil, 'gluon-web-admin', {
 			hidenav = true,
 		})
@@ -132,7 +134,7 @@ local function action_upgrade(http, renderer)
 end
 
 
-local has_platform = fs.access("/lib/upgrade/platform.sh")
+local has_platform = unistd.access("/lib/upgrade/platform.sh")
 if has_platform then
 	local upgrade = entry({"admin", "upgrade"}, call(action_upgrade), _("Upgrade firmware"), 90)
 	upgrade.filehandler = filehandler
diff --git a/package/gluon-web-admin/luasrc/lib/gluon/config-mode/model/admin/remote.lua b/package/gluon-web-admin/luasrc/lib/gluon/config-mode/model/admin/remote.lua
index 52c29ab060bbff98872f66c44e1fbe90b9016b46..756b9bd1954bdfb519ce87aaf9f2a57f520e685d 100644
--- a/package/gluon-web-admin/luasrc/lib/gluon/config-mode/model/admin/remote.lua
+++ b/package/gluon-web-admin/luasrc/lib/gluon/config-mode/model/admin/remote.lua
@@ -10,24 +10,28 @@ You may obtain a copy of the License at
 	http://www.apache.org/licenses/LICENSE-2.0
 ]]--
 
-local nixio = require "nixio"
-local fs = require "nixio.fs"
-local util = require "gluon.util"
-local site = require "gluon.site"
+local util = require 'gluon.util'
+local site = require 'gluon.site'
+
+local fcntl = require 'posix.fcntl'
+local unistd = require 'posix.unistd'
+local wait = require 'posix.sys.wait'
 
 local f_keys = Form(translate("SSH keys"), translate("You can provide your SSH keys here (one per line):"), 'keys')
 local s = f_keys:section(Section)
 local keys = s:option(TextValue, "keys")
 keys.wrap = "off"
 keys.rows = 5
-keys.default = fs.readfile("/etc/dropbear/authorized_keys") or ""
+keys.default = util.readfile("/etc/dropbear/authorized_keys") or ""
 
 function keys:write(value)
 	value = util.trim(value:gsub("\r", ""))
 	if value ~= "" then
-		fs.writefile("/etc/dropbear/authorized_keys", value .. "\n")
+		local f = io.open("/etc/dropbear/authorized_keys", "w")
+		f:write(value, "\n")
+		f:close()
 	else
-		fs.remove("/etc/dropbear/authorized_keys")
+		unistd.unlink("/etc/dropbear/authorized_keys")
 	end
 end
 
@@ -72,34 +76,34 @@ function pw2.cfgvalue()
 end
 
 local function set_password(password)
-	local inr, inw = nixio.pipe()
-	local pid = nixio.fork()
+	local inr, inw = unistd.pipe()
+	local pid = unistd.fork()
 
 	if pid < 0 then
 		return false
 	elseif pid == 0 then
-		inw:close()
+		unistd.close(inw)
 
-		local null = nixio.open('/dev/null', 'w')
-		nixio.dup(null, nixio.stderr)
-		nixio.dup(null, nixio.stdout)
-		if null:fileno() > 2 then
-			null:close()
+		local null = fcntl.open('/dev/null', fcntl.O_WRONLY)
+		unistd.dup2(null, unistd.STDOUT_FILENO)
+		unistd.dup2(null, unistd.STDERR_FILENO)
+		if null > 2 then
+			unistd.close(null)
 		end
 
-		nixio.dup(inr, nixio.stdin)
-		inr:close()
+		unistd.dup2(inr, unistd.STDIN_FILENO)
+		unistd.close(inr)
 
-		nixio.execp('passwd')
+		unistd.execp('passwd', {[0] = 'passwd'})
 		os.exit(127)
 	end
 
-	inr:close()
+	unistd.close(inr)
 
-	inw:write(string.format('%s\n%s\n', password, password))
-	inw:close()
+	unistd.write(inw, string.format('%s\n%s\n', password, password))
+	unistd.close(inw)
 
-	local wpid, status, code = nixio.waitpid(pid)
+	local wpid, status, code = wait.wait(pid)
 	return wpid and status == 'exited' and code == 0
 end
 
diff --git a/package/gluon-web-model/luasrc/usr/lib/lua/gluon/web/model.lua b/package/gluon-web-model/luasrc/usr/lib/lua/gluon/web/model.lua
index 8c968dfe6a86bd5ca4e23678e75ca6c0b1e1cbf0..513fb65e59ece23ddff8212487df93faa3cdb073 100644
--- a/package/gluon-web-model/luasrc/usr/lib/lua/gluon/web/model.lua
+++ b/package/gluon-web-model/luasrc/usr/lib/lua/gluon/web/model.lua
@@ -4,7 +4,7 @@
 
 module('gluon.web.model', package.seeall)
 
-local fs = require 'nixio.fs'
+local unistd = require 'posix.unistd'
 local classes = require 'gluon.web.model.classes'
 
 local util = require 'gluon.web.util'
@@ -38,7 +38,7 @@ return function(config, http, renderer, name, pkg)
 	local modeldir = config.base_path .. '/model/'
 	local filename = modeldir..name..'.lua'
 
-	if not fs.access(filename) then
+	if not unistd.access(filename) then
 		error("Model '" .. name .. "' not found!")
 	end
 
diff --git a/package/gluon-web-wifi-config/luasrc/lib/gluon/config-mode/model/admin/wifi-config.lua b/package/gluon-web-wifi-config/luasrc/lib/gluon/config-mode/model/admin/wifi-config.lua
index 0eaf4afad45f4fac8807e95116efd2d3691cf19f..bfb5b9cf088f7b47824d84b413c6fb33093684ae 100644
--- a/package/gluon-web-wifi-config/luasrc/lib/gluon/config-mode/model/admin/wifi-config.lua
+++ b/package/gluon-web-wifi-config/luasrc/lib/gluon/config-mode/model/admin/wifi-config.lua
@@ -1,4 +1,3 @@
-local fs = require 'nixio.fs'
 local iwinfo = require 'iwinfo'
 local uci = require("simple-uci").cursor()
 local util = require 'gluon.util'
diff --git a/package/gluon-web/Makefile b/package/gluon-web/Makefile
index 1ecca59ea0a43b51e595ca07d6e751eedcb2ff6a..8585a493a80de59cf5ca91382ae9b02c39dd2a47 100644
--- a/package/gluon-web/Makefile
+++ b/package/gluon-web/Makefile
@@ -9,7 +9,7 @@ include ../gluon.mk
 
 define Package/gluon-web
   TITLE:=Minimal Lua web framework derived from LuCI
-  DEPENDS:=+lua-jsonc +luci-lib-nixio
+  DEPENDS:=+lua-jsonc +luaposix
 endef
 
 define lang-config
diff --git a/package/gluon-web/luasrc/usr/lib/lua/gluon/web/cgi.lua b/package/gluon-web/luasrc/usr/lib/lua/gluon/web/cgi.lua
index 8d8c648a47a43bc8a5f317dbc33a152a34729dcb..a0553c20189ca68ff03d79c9d2b7eb988abacd8d 100644
--- a/package/gluon-web/luasrc/usr/lib/lua/gluon/web/cgi.lua
+++ b/package/gluon-web/luasrc/usr/lib/lua/gluon/web/cgi.lua
@@ -2,7 +2,7 @@
 -- Copyright 2017 Matthias Schiffer <mschiffer@universe-factory.net>
 -- Licensed to the public under the Apache License 2.0.
 
-local nixio = require 'nixio'
+local stdlib = require 'posix.stdlib'
 local http = require 'gluon.web.http'
 local dispatcher = require 'gluon.web.dispatcher'
 
@@ -27,9 +27,10 @@ local function limitsource(handle, limit)
 end
 
 return function(config)
+	local env = stdlib.getenv()
 	dispatcher(config, http.Http(
-		nixio.getenv(),
-		limitsource(io.stdin, tonumber(nixio.getenv("CONTENT_LENGTH"))),
+		env,
+		limitsource(io.stdin, tonumber(env.CONTENT_LENGTH)),
 		io.stdout
 	))
 end
diff --git a/package/gluon-web/luasrc/usr/lib/lua/gluon/web/dispatcher.lua b/package/gluon-web/luasrc/usr/lib/lua/gluon/web/dispatcher.lua
index 64449cd0d0fe4e145b6e89812ca84dfa051107e3..b003a08609565c137e8383c330f2ba9dd87e2072 100644
--- a/package/gluon-web/luasrc/usr/lib/lua/gluon/web/dispatcher.lua
+++ b/package/gluon-web/luasrc/usr/lib/lua/gluon/web/dispatcher.lua
@@ -3,7 +3,7 @@
 -- Copyright 2017-2018 Matthias Schiffer <mschiffer@universe-factory.net>
 -- Licensed to the public under the Apache License 2.0.
 
-local fs = require "nixio.fs"
+local glob = require 'posix.glob'
 local json = require "jsonc"
 local tpl = require "gluon.web.template"
 local util = require "gluon.web.util"
@@ -158,10 +158,10 @@ local function dispatch(config, http, request)
 			ctl()
 		end
 
-		for path in (fs.glob(base .. "*.lua") or function() end) do
+		for _, path in ipairs(glob.glob(base .. "*.lua") or {}) do
 			load_ctl(path)
 		end
-		for path in (fs.glob(base .. "*/*.lua") or function() end) do
+		for _, path in ipairs(glob.glob(base .. "*/*.lua") or {}) do
 			load_ctl(path)
 		end
 	end
diff --git a/package/gluon-web/luasrc/usr/lib/lua/gluon/web/http.lua b/package/gluon-web/luasrc/usr/lib/lua/gluon/web/http.lua
index 2cf83de096b31bc0e9208838e0e45c30ccb435cd..01cf8a9063774922f440336ae3d4983d1ae5cb47 100644
--- a/package/gluon-web/luasrc/usr/lib/lua/gluon/web/http.lua
+++ b/package/gluon-web/luasrc/usr/lib/lua/gluon/web/http.lua
@@ -4,7 +4,6 @@
 
 local string = string
 local table = table
-local nixio = require "nixio"
 local protocol = require "gluon.web.http.protocol"
 local util  = require "gluon.web.util"
 
diff --git a/package/gluon-web/luasrc/usr/lib/lua/gluon/web/i18n.lua b/package/gluon-web/luasrc/usr/lib/lua/gluon/web/i18n.lua
index ad42c3a3cd173e9db54d0a0acf4979e529ee344b..80424505b95dd82144b4a0ad25668adf5cd6dc07 100644
--- a/package/gluon-web/luasrc/usr/lib/lua/gluon/web/i18n.lua
+++ b/package/gluon-web/luasrc/usr/lib/lua/gluon/web/i18n.lua
@@ -1,8 +1,8 @@
 -- Copyright 2018 Matthias Schiffer <mschiffer@universe-factory.net>
 -- Licensed to the public under the Apache License 2.0.
 
-local tparser = require "gluon.web.template.parser"
-local fs = require "nixio.fs"
+local tparser = require 'gluon.web.template.parser'
+local unistd = require 'posix.unistd'
 
 
 return function(config)
@@ -20,7 +20,7 @@ return function(config)
 	local function load_catalog(lang, pkg)
 		if pkg then
 			local file = i18n_file(lang, pkg)
-			local cat = fs.access(file) and tparser.load_catalog(file)
+			local cat = unistd.access(file) and tparser.load_catalog(file)
 
 			if cat then return cat end
 		end
@@ -32,7 +32,7 @@ return function(config)
 	local i18n = {}
 
 	function i18n.supported(lang)
-		return lang == 'en' or fs.access(i18n_file(lang, 'gluon-web'))
+		return lang == 'en' or unistd.access(i18n_file(lang, 'gluon-web'))
 	end
 
 	function i18n.load(lang, pkg)