Build an Intranet Server on Linux
Set up a complete small-office intranet on one Linux box: Nginx web server, dnsmasq local DNS, Samba file sharing, and a Wiki.js team wiki.
Before you start
- ▸A dedicated machine or VM with at least 2 GB RAM and 20 GB disk
- ▸Root or sudo access on the server
- ▸Basic familiarity with editing text files and running systemctl
- ▸LAN access with ability to configure the router's DHCP settings
A single Linux box can serve an entire small office: internal websites, shared files, local DNS resolution, and a team wiki. This guide walks through standing up all four services on one machine, keeping them isolated enough to maintain but simple enough for a team without a dedicated ops department. We use a Debian/Ubuntu base for most examples and note Fedora/RHEL and Arch differences inline.
Prerequisites and Planning
Assign the server a static IP on your LAN before touching anything else. Every service here depends on a stable address. On a systemd-networkd setup, edit your network configuration; on NetworkManager (most desktops and many servers), use nmcli:
sudo nmcli con mod "Wired connection 1" \
ipv4.addresses 192.168.1.10/24 \
ipv4.gateway 192.168.1.1 \
ipv4.dns 127.0.0.1 \
ipv4.method manual
sudo nmcli con up "Wired connection 1"
Replace the address and interface name to match your network. Setting ipv4.dns 127.0.0.1 points the server at its own DNS daemon, which you will install shortly.
Install and Harden the Web Server
Nginx is a solid choice for an internal web server: low memory footprint, clean virtual-host config, and easy to extend later.
Install Nginx
# Debian / Ubuntu
sudo apt update && sudo apt install -y nginx
# Fedora / RHEL / Rocky
sudo dnf install -y nginx
# Arch
sudo pacman -S --noconfirm nginx
sudo systemctl enable --now nginx
Create the Intranet Virtual Host
Put the intranet root under /srv/intranet and write a minimal virtual host. Adjust the hostname to whatever you will add to DNS in the next section.
sudo mkdir -p /srv/intranet/html
echo '<h1>Office Intranet</h1>' | sudo tee /srv/intranet/html/index.html
sudo tee /etc/nginx/sites-available/intranet <<'EOF'
server {
listen 80;
server_name intranet.office.lan;
root /srv/intranet/html;
index index.html;
location / { try_files $uri $uri/ =404; }
}
EOF
sudo ln -s /etc/nginx/sites-available/intranet \
/etc/nginx/sites-enabled/intranet
sudo nginx -t && sudo systemctl reload nginx
On Fedora/RHEL, there is no sites-available convention; drop the file in /etc/nginx/conf.d/intranet.conf instead and skip the symlink step.
Set Up Local DNS with dnsmasq
dnsmasq is a lightweight DNS forwarder that also handles DHCP if you need it. It lets you give every intranet service a human-readable name like intranet.office.lan without modifying every client's hosts file.
Install dnsmasq
# Debian / Ubuntu
sudo apt install -y dnsmasq
# Fedora / RHEL / Rocky
sudo dnf install -y dnsmasq
# Arch
sudo pacman -S --noconfirm dnsmasq
Note for Ubuntu 18.04+ users: systemd-resolved occupies port 53 by default. Disable its stub listener before starting dnsmasq:
sudo sed -i 's/#DNSStubListener=yes/DNSStubListener=no/' \
/etc/systemd/resolved.conf
sudo systemctl restart systemd-resolved
Configure dnsmasq
sudo tee /etc/dnsmasq.d/office.conf <<'EOF'
# Listen only on the LAN interface and loopback
interface=lo
bind-interfaces
# Forward unresolved queries upstream
server=1.1.1.1
server=8.8.8.8
# Local zone — never forward .lan queries
local=/office.lan/
domain=office.lan
# A records for intranet services
address=/intranet.office.lan/192.168.1.10
address=/files.office.lan/192.168.1.10
address=/wiki.office.lan/192.168.1.10
EOF
sudo systemctl enable --now dnsmasq
Point your router's DHCP server to 192.168.1.10 as the primary DNS so every device on the LAN resolves *.office.lan automatically. If you cannot change the router, push the DNS address via a static DHCP lease or configure workstations individually.
Samba File Sharing
Samba exposes a share that Windows, macOS, and Linux clients can all mount with no extra client software.
Install Samba
# Debian / Ubuntu
sudo apt install -y samba
# Fedora / RHEL / Rocky
sudo dnf install -y samba samba-common
# Arch
sudo pacman -S --noconfirm samba
Create the Share Directory and Users
sudo mkdir -p /srv/shared
sudo chown root:staff /srv/shared # or create a dedicated group
sudo chmod 2775 /srv/shared
Add a Samba password for each user who needs access. The Linux account must already exist.
sudo smbpasswd -a alice
Configure Samba
Back up the default config and write a minimal one:
sudo cp /etc/samba/smb.conf /etc/samba/smb.conf.bak
sudo tee /etc/samba/smb.conf <<'EOF'
[global]
workgroup = OFFICE
server string = Office File Server
netbios name = fileserver
security = user
map to guest = never
dns proxy = no
[Shared]
path = /srv/shared
browseable = yes
writable = yes
valid users = @staff
create mask = 0664
directory mask = 2775
EOF
sudo testparm -s && sudo systemctl enable --now smbd nmbd
On Fedora/RHEL, also allow Samba through SELinux and the local firewall:
sudo setsebool -P samba_export_all_rw 1
sudo firewall-cmd --permanent --add-service=samba
sudo firewall-cmd --reload
Deploy a Team Wiki with Wiki.js
Wiki.js is a modern Node.js wiki with a clean editor, Markdown support, and access controls. It runs well on modest hardware and stores content in PostgreSQL or SQLite.
Install Node.js and Wiki.js
# Install Node.js 20 LTS (all distros via NodeSource)
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt install -y nodejs # Debian/Ubuntu
# For Fedora: use 'dnf' after running the NodeSource setup_20.x script
sudo mkdir -p /opt/wikijs && cd /opt/wikijs
sudo curl -fsSL https://github.com/Requarks/wiki/releases/latest/download/wiki-js.tar.gz \
| sudo tar xz
sudo cp config.sample.yml config.yml
Edit /opt/wikijs/config.yml to set the port (e.g., 3000) and database. For a small team, SQLite requires the least setup:
sudo sed -i 's/type: postgres/type: sqlite/' /opt/wikijs/config.yml
sudo sed -i 's/# storage:/storage: \/opt\/wikijs\/wiki.db/' /opt/wikijs/config.yml
Run Wiki.js as a systemd Service
sudo tee /etc/systemd/system/wikijs.service <<'EOF'
[Unit]
Description=Wiki.js
After=network.target
[Service]
Type=simple
ExecStart=/usr/bin/node /opt/wikijs/server
WorkingDirectory=/opt/wikijs
Restart=always
User=www-data
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload
sudo systemctl enable --now wikijs
On Fedora/RHEL, change User=www-data to User=nginx or create a dedicated wikijs system user.
Proxy Wiki.js Through Nginx
Add a virtual host that forwards traffic from wiki.office.lan to the local Node process:
sudo tee /etc/nginx/sites-available/wiki <<'EOF'
server {
listen 80;
server_name wiki.office.lan;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
EOF
sudo ln -s /etc/nginx/sites-available/wiki \
/etc/nginx/sites-enabled/wiki
sudo nginx -t && sudo systemctl reload nginx
Firewall Configuration
Allow only the ports your services actually need. Examples for ufw (Debian/Ubuntu) and firewalld (Fedora/RHEL):
# ufw — Debian / Ubuntu
sudo ufw allow from 192.168.1.0/24 to any port 80 comment 'Intranet HTTP'
sudo ufw allow from 192.168.1.0/24 to any port 53 comment 'DNS'
sudo ufw allow from 192.168.1.0/24 to any port 445 comment 'Samba'
sudo ufw allow from 192.168.1.0/24 to any port 137:139/tcp comment 'Samba NetBIOS'
sudo ufw enable
# firewalld — Fedora / RHEL / Rocky
sudo firewall-cmd --permanent --add-rich-rule=\
'rule family=ipv4 source address=192.168.1.0/24 service name=http accept'
sudo firewall-cmd --permanent --add-rich-rule=\
'rule family=ipv4 source address=192.168.1.0/24 service name=dns accept'
sudo firewall-cmd --reload
Verify Everything Works
Run these checks from a client machine on the same subnet:
# DNS resolution
dig intranet.office.lan @192.168.1.10 +short
# Web server
curl -si http://intranet.office.lan | head -5
# Wiki
curl -si http://wiki.office.lan | head -5
# Samba
smbclient -L 192.168.1.10 -U alice
Each command should return a valid response. If DNS is not resolving, confirm dnsmasq is running (systemctl status dnsmasq) and that the client's DNS server is set to 192.168.1.10.
Troubleshooting
- Port 53 conflict on Ubuntu: If dnsmasq fails to start, check whether systemd-resolved is still holding port 53 with
sudo ss -tulpn | grep :53. Re-apply the DNSStubListener fix and restart resolved. - Samba auth failures: Ensure the user has a Samba password set via
smbpasswdand is a member of thevalid usersgroup. Checkjournalctl -u smbdfor details. - Wiki.js not starting: Run
node /opt/wikijs/servermanually as the service user to see startup errors directly. Common causes are a missingconfig.ymlkey or a port already in use. - SELinux blocking Nginx proxy: On RHEL/Fedora, allow Nginx to make network connections:
sudo setsebool -P httpd_can_network_connect 1. - Clients not resolving .lan names: If you cannot change the router's DNS, push DNS via a static DHCP reservation for each workstation, or add a dnsmasq instance to the router if it runs OpenWrt/DD-WRT.
Frequently asked questions
- Can I add HTTPS to the intranet services without a public domain?
- Yes. Use a private CA with mkcert or step-ca to issue LAN certificates for your .office.lan names. Install the CA root cert on each client device and Nginx will serve TLS just like a public site.
- Why use dnsmasq instead of BIND for a small office?
- dnsmasq needs a single config file and under 10 MB of RAM. BIND is more powerful but brings real operational overhead for a setup that only needs a handful of A records and upstream forwarding.
- Will Samba shares work with macOS clients?
- Yes. macOS has native SMB support. In Finder use Go → Connect to Server and enter smb://files.office.lan. Authenticate with the Samba username and password you set via smbpasswd.
- How do I back up the wiki and shared files?
- For Wiki.js with SQLite, copy /opt/wikijs/wiki.db on a schedule with a systemd timer or cron job. For Samba, rsync /srv/shared to an external drive or a remote host. Always stop or flush the SQLite file before copying to avoid corruption.
- What if the office grows and one box is no longer enough?
- Separate services onto dedicated VMs or containers (LXC, Docker, or KVM). The Nginx reverse proxy approach used here for Wiki.js scales naturally — just update the proxy_pass address and the DNS A record when you move a service.
Related guides
How to Change the Webmin Port
Move Webmin off its default port 10000 by editing miniserv.conf, updating your firewall (ufw, firewalld, or nftables), and restarting the service.
Configure a UPS on Linux with NUT
Install and configure Network UPS Tools (NUT) on Linux to detect your UPS, load the right driver, and trigger a safe automatic shutdown when battery runs low.
How to Configure the SMTP HELO/EHLO Name
Set the correct SMTP HELO/EHLO hostname in Postfix and Sendmail, configure FCrDNS records, and verify your mail server won't be rejected or spam-flagged.
Configure a Squid Proxy with Webmin
Install Squid and the Webmin Squid module, configure disk and memory caching, set LAN access controls, and verify the proxy is serving cached responses.