Log the diff of a Apache httpd config file and reload the service with Python and systemd Using systemd's .path unit to run a Python script whenever a httpd config file is changed. The Python script creates a backup of the config and logs the differences of the newly modified and backup file. After that it checks the syntax of the httpd config and gracefully reloads the web server service.

The Python script:

#!/path/to/python 

import difflib
import logging
import shutil
import subprocess
import sys
from pathlib import Path
import yaml

logging.basicConfig(format='%(levelname)s - %(message)s', level=logging.INFO)


class HttpdMonitor:

    def __init__(self, config_file):
        self.config_file = config_file
        self.config = self.load_config()
        self.httpd_conf = self.config['httpd-conf']
        self.backup_file = self.config['httpd-conf'] + '.bak'
        self.syntax_check_cmd = self.config['syntax-check-cmd'].split()
        self.reload_cmd = self.config['reload-cmd'].split()

    def load_config(self):
        with open(self.config_file, 'r') as stream:
            return yaml.safe_load(stream)

    def make_backup(self):
        try:
            shutil.copy2(self.httpd_conf, self.backup_file)
        except IOError as e:
            logging.exception("Making backup failed: %s", e)

    def find_diff(self):
        with open(self.httpd_conf, 'r') as f, open(self.backup_file, 'r') as bak:
            diffs = list(
                difflib.unified_diff(f.readlines(), bak.readlines(), fromfile=self.httpd_conf, tofile=self.backup_file))
            return diffs

    def log_diff(self):
        try:
            diffs = self.find_diff()
            logging.info('Differences found: \n%s', ''.join(diffs))
        except IOError as e:
            logging.exception('Finding diff failed: %s', e)

    def call_httpd_commands(self):
        subprocess.run(self.syntax_check_cmd, check=True)
        subprocess.run(self.reload_cmd, check=True)

    def reload_httpd(self):
        try:
            self.call_httpd_commands()
        except subprocess.CalledProcessError as e:
            logging.exception("Reloading failed: %s", e)

    def run(self):
        if not Path(self.backup_file).is_file():
            self.make_backup()
            return
        self.log_diff()
        self.make_backup()
        self.reload_httpd()


if __name__ == '__main__':
    httpd_monitor = HttpdMonitor(sys.argv[1])
    httpd_monitor.run()


This uses a YAML file to specify the httpd config file and also the commands to run, sort of like this:

---
httpd-conf: /path/to/file
syntax-check-cmd: httpd -t
reload-cmd: httpd -k graceful


Using systemd's .path unit the script get fired whenever there's something changed in the httpd config file. For example httpd-monitor.path:

[Unit]
Description=Monitor the httpd conf for changes

[Path]
PathChanged=/path/to/file
Unit=httpd-monitor.service

[Install]
WantedBy=multi-user.target


And httpd-monitor.service:

[Unit]
Description=Do backup, diff changes and reload httpd

[Service]
Restart=no
Type=simple
ExecStart=/path/to/httpd_monitor.py /path/to/httpd-monitor.yaml

[Install]
WantedBy=multi-user.target


So I'd first run systemd start httpd-monitor.service just to create the initial backup (the if condition within the run() method should take care of it) and then just systemd enable --now httpd-monitor.path to enable the .path unit. 
Probably not the simplest of solutions so I'm very open to any feedback!
As a whole your approach looks good, except for a couple of issues I'd suggest to fix:


__init__ method
Avoid repetitive indexing of nested structures like self.config['httpd-conf'].Instead use assigned instance variable with f-string formatting:

def __init__(self, config_file):
    self.config_file = config_file
    self.config = self.load_config()
    self.httpd_conf = self.config['httpd-conf']
    self.backup_file = f'{self.httpd_conf}.bak'
    self.syntax_check_cmd = self.config['syntax-check-cmd'].split()
    self.reload_cmd = self.config['reload-cmd'].split()

find_diff method
No need to convert a generator returned by difflib.unified_diff function into list with diffs = list(...) - the subsequent ''.join(diffs) itself will take care of consuming generator.
call_httpd_commands method
This method is redundant as its main effect is reloading httpd server, but you already have the appropriate method for that purpose - reload_httpd. Thus call_httpd_commands method's statements are just moved there and the old method is eliminated:

def reload_httpd(self):
    try:
        subprocess.run(self.syntax_check_cmd, check=True)
        subprocess.run(self.reload_cmd, check=True)
    except subprocess.CalledProcessError as e:
        logging.exception("Reloading failed: %s", e)