🚀 Executive Summary

TL;DR: Manual Nmap security audits are inefficient and error-prone; this solution automates the process using a Python script and cron job. It runs scheduled Nmap scans, compares current results with previous ones, and generates diff files to quickly identify network changes like newly opened ports.

🎯 Key Takeaways

  • The automated audit system relies on Nmap for scanning, Python 3 with `python-nmap` and `python-dotenv` for scripting, and cron for scheduling.
  • The Python script `scanner_agent.py` loads configuration from `config.env`, executes Nmap, saves the scan output, and uses `difflib` to compare new results against the latest previous scan, generating a diff file upon detecting changes.
  • Critical Nmap arguments like `-T4` (aggressive timing) and `-F` (fast scan) can be configured, with a `Pro Tip` suggesting `-sV` for deeper service/version detection, noting its increased time and network noise.

Automated Security Audit: Running Nmap Scans via Cron and Diffing Results

Automated Security Audit: Running Nmap Scans via Cron and Diffing Results

Hey everyone, Darian Vance here. Let me tell you, I used to dread the first Monday of every month. That was my day for manually running Nmap scans on our critical subnets and comparing them to the previous month’s results. It was tedious, error-prone, and honestly, a huge time sink. After one too many cups of coffee spent staring at terminal outputs, I knew there had to be a better way. This little Python script and cron job setup saved me hours and, more importantly, caught a rogue port someone opened on a staging server before it became a real problem. Let’s build it.

Prerequisites

Before we dive in, make sure you have the following ready:

  • Nmap: The network scanner itself needs to be installed on the machine running the script.
  • Python 3: This script is written for Python 3.
  • Required Python Libraries: You’ll need python-nmap to interface with Nmap and python-dotenv to handle our configuration. You’d typically install these with pip.
  • Cron Access: You’ll need access to a task scheduler like cron to automate the script.

The Step-by-Step Guide

We’re going to create a simple system: a Python script runs an Nmap scan, saves the output, and compares it to the last known good scan. If there are any differences, it generates a diff file for you to review.

Step 1: The Project Structure

I’m a big believer in keeping projects clean. For this, I recommend a simple structure. You’ll need a main Python script, a configuration file, and a directory to store the scan results. I’ll skip the standard virtualenv setup since you likely have your own workflow for that. Let’s assume you have a project folder ready to go with these items inside:

  • scanner_agent.py – Our main Python script.
  • config.env – Where we’ll store our settings.
  • A directory named scan_results/ – This is where the output files will live.

Step 2: The Configuration File

Separating configuration from code is a core DevOps principle. It makes things easier to manage without touching the logic. Let’s create a file named config.env.


# The target IP address, range, or hostname
TARGET_HOST=192.168.1.0/24

# Nmap arguments. -T4 is aggressive, -F is fast (fewer ports)
NMAP_ARGS=-T4 -F

# Directory to store scan results
OUTPUT_DIR=scan_results

Step 3: The Python Scanner Script

This is where the magic happens. We’ll create scanner_agent.py. The script will load our configuration, run the scan, and then compare the new results with the most recent previous result.

Here’s the full script. I’ve added comments to walk you through the logic.


import nmap
import os
from datetime import datetime
import difflib
from dotenv import load_dotenv

def load_configuration():
    """Loads configuration from the config.env file."""
    load_dotenv('config.env')
    config = {
        'target': os.getenv('TARGET_HOST'),
        'args': os.getenv('NMAP_ARGS'),
        'dir': os.getenv('OUTPUT_DIR')
    }
    # Basic validation
    if not all(config.values()):
        print("Error: Configuration is missing. Check your config.env file.")
        return None
    return config

def run_nmap_scan(target, arguments):
    """Runs an Nmap scan and returns the raw output."""
    print(f"Starting Nmap scan on {target} with arguments: {arguments}...")
    try:
        nm = nmap.PortScanner()
        nm.scan(hosts=target, arguments=arguments)
        scan_output = []
        for host in nm.all_hosts():
            scan_output.append(f"Host: {host} ({nm[host].hostname()})")
            scan_output.append(f"State: {nm[host].state()}")
            for proto in nm[host].all_protocols():
                scan_output.append(f"----------\nProtocol: {proto}")
                lport = sorted(nm[host][proto].keys())
                for port in lport:
                    scan_output.append(f"port : {port}\tstate : {nm[host][proto][port]['state']}")
        print("Scan completed successfully.")
        return "\n".join(scan_output)
    except Exception as e:
        print(f"An error occurred during the scan: {e}")
        return None

def compare_and_save_results(output_dir, new_scan_data):
    """Compares the new scan with the latest existing scan and saves the results."""
    # Ensure the output directory exists
    if not os.path.exists(output_dir):
        print(f"Output directory '{output_dir}' not found. Please create it.")
        return

    # Find the latest scan file
    scan_files = sorted([f for f in os.listdir(output_dir) if f.endswith('.txt')])
    latest_scan_file = os.path.join(output_dir, scan_files[-1]) if scan_files else None

    # Define the new filename
    timestamp = datetime.now().strftime("%Y%m%d-%H%M%S")
    new_scan_filename = os.path.join(output_dir, f"scan_{timestamp}.txt")

    # Save the new scan result
    with open(new_scan_filename, 'w') as f:
        f.write(new_scan_data)
    print(f"New scan saved to {new_scan_filename}")

    # If there's a previous scan, compare them
    if latest_scan_file:
        print(f"Comparing with latest scan: {latest_scan_file}")
        with open(latest_scan_file, 'r') as f1, open(new_scan_filename, 'r') as f2:
            fromlines = f1.readlines()
            tolines = f2.readlines()
        
        diff = difflib.unified_diff(fromlines, tolines, fromfile='previous_scan', tofile='current_scan')
        diff_output = list(diff)

        if diff_output:
            diff_filename = os.path.join(output_dir, f"diff_{timestamp}.txt")
            with open(diff_filename, 'w') as f:
                f.writelines(diff_output)
            print(f"Changes detected! Diff file created at {diff_filename}")
        else:
            print("No changes detected since the last scan.")

def main():
    """Main function to orchestrate the scan and comparison."""
    config = load_configuration()
    if not config:
        return # Exit if config is invalid
    
    scan_data = run_nmap_scan(config['target'], config['args'])
    
    if scan_data:
        compare_and_save_results(config['dir'], scan_data)

if __name__ == "__main__":
    main()

Pro Tip: The Nmap arguments you choose are critical. For a quick check, -F (Fast scan) is great. For a deeper look, I often use -sV to probe open ports to determine service/version info. Be warned, though: more comprehensive scans take significantly longer and can be “noisier” on the network.

Step 4: Automating with Cron

The final piece is automation. We want this script to run on a schedule without any manual intervention. This is a perfect job for cron.

You’ll need to edit your user’s crontab file to add a new job. The following line will run our script every Monday at 2:00 AM.

0 2 * * 1 python3 scanner_agent.py

Make sure you run this cron job from within your project directory, or provide the full path to your script. The command assumes python3 is in the cron user’s PATH and that the script is in the directory from which cron executes.


Where I Usually Mess Up (Common Pitfalls)

Even simple setups have their quirks. Here are a few things that have tripped me up in the past:

  • Permissions, Permissions, Permissions: The cron user needs permission to run Nmap (which sometimes requires elevated privileges) and write permissions for the scan_results directory. This is the number one cause of silent failures.
  • Environment Paths: Cron runs with a very minimal environment. If your script fails under cron but works manually, it’s almost always a PATH issue. The cron command I provided assumes python3 is in a standard location known to the system.
  • First Run Fails Differently: The very first time the script runs, there will be no “previous” scan to compare against. My script handles this gracefully, but it’s something to be aware of. The real value starts from the second run onwards.

Conclusion

And there you have it. A simple, automated way to keep an eye on your network’s open ports. This isn’t a replacement for a full-blown Intrusion Detection System, of course, but for a lightweight, zero-cost monitoring solution, it’s incredibly effective. You’ve now turned a manual, repetitive task into an automated security check that alerts you when something changes. It’s a small investment of time that pays big dividends in awareness and peace of mind. Stay safe out there.

Darian Vance - Lead Cloud Architect

Darian Vance

Lead Cloud Architect & DevOps Strategist

With over 12 years in system architecture and automation, Darian specializes in simplifying complex cloud infrastructures. An advocate for open-source solutions, he founded TechResolve to provide engineers with actionable, battle-tested troubleshooting guides and robust software alternatives.


🤖 Frequently Asked Questions

âť“ How can I automate network port scanning for security audits?

You can automate Nmap scans using a Python script scheduled by cron. The script runs Nmap on a target, saves the current output, and compares it to the most recent previous scan to identify any changes in open ports or services.

âť“ How does this automated Nmap solution compare to more comprehensive security tools?

This solution provides a lightweight, zero-cost method for monitoring network changes and open ports. While effective for basic awareness, it is not a replacement for full-blown Intrusion Detection Systems (IDS) or dedicated vulnerability management platforms, which offer deeper analysis and threat detection capabilities.

âť“ What are common implementation pitfalls when setting up Nmap scans with cron, and how can they be resolved?

Common pitfalls include cron user permissions (Nmap often requires elevated privileges, and the script needs write access to the `scan_results` directory) and environment PATH issues (cron runs with a minimal environment). Ensure the cron user has necessary permissions and use full paths for commands like `python3` if they are not found in cron’s PATH.

Leave a Reply

Discover more from TechResolve - SaaS Troubleshooting & Software Alternatives

Subscribe now to keep reading and get access to the full archive.

Continue reading