projectdiscovery/nuclei · error

headless mode (-headless) is required if -ho, -sb, -sc or -l

Error message

headless mode (-headless) is required if -ho, -sb, -sc or -lha are set

What it means

Nuclei configures its network dialer during protocol state initialization. When both -source-ip and -interface are set, isIpAssociatedWithInterface must confirm the IP is assigned to that interface; a mismatch aborts startup. This prevents binding traffic to an address the OS would not route through that interface.

Source

Thrown at internal/runner/options.go:160

// validateOptions validates the configuration options passed
func ValidateOptions(options *types.Options) error {
	if err := validateOptions.Struct(options); err != nil {
		if _, ok := err.(*validator.InvalidValidationError); ok {
			return err
		}
		errs := []string{}
		for _, err := range err.(validator.ValidationErrors) {
			errs = append(errs, err.Namespace()+": "+err.Tag())
		}
		return errors.Wrap(errors.New(strings.Join(errs, ", ")), "validation failed for these fields")
	}
	if options.Verbose && options.Silent {
		return errors.New("both verbose and silent mode specified")
	}

	if (options.HeadlessOptionalArguments != nil || options.ShowBrowser || options.UseInstalledChrome) && !options.Headless {
		return errors.New("headless mode (-headless) is required if -ho, -sb, -sc or -lha are set")
	}

	if options.FollowHostRedirects && options.FollowRedirects {
		return errors.New("both follow host redirects and follow redirects specified")
	}
	if options.ShouldFollowHTTPRedirects() && options.DisableRedirects {
		return errors.New("both follow redirects and disable redirects specified")
	}
	// loading the proxy server list from file or cli and test the connectivity
	if err := loadProxyServers(options); err != nil {
		return err
	}
	if options.Validate {
		validateTemplatePaths(options.Logger, config.DefaultConfig.TemplatesDirectory, options.Templates, options.Workflows)
	}
	if options.DAST {
		if err := validateDASTOptions(options); err != nil {
			return err

View on GitHub (pinned to 265b3a3dec)

Solutions

  1. Run ip addr show <interface> and use one of its addresses in -source-ip
  2. Point -interface at the interface that actually owns the IP
  3. Drop one of the two flags and let the other drive selection alone

Example fix

# before
nuclei -source-ip 10.0.0.5 -interface eth0   # eth0 actually has 192.168.1.20
# after
nuclei -source-ip 192.168.1.20 -interface eth0
Defensive patterns

Strategy: validation

Validate before calling

import "net"

func ipOnInterface(ip, iface string) bool {
    i, err := net.InterfaceByName(iface)
    if err != nil {
        return false
    }
    addrs, _ := i.Addrs()
    for _, a := range addrs {
        if ipnet, ok := a.(*net.IPNet); ok && ipnet.IP.String() == ip {
            return true
        }
    }
    return false
}

Prevention

When it happens

Trigger: nuclei -source-ip 192.168.1.10 -interface eth0 where eth0 holds a different address or only IPv6; equivalent lib/nuclei options with both fields set.

Common situations: VPN environments where the IP lives on utun/wg0 while the flag names eth0; container or NAT setups; command lines copied between machines.

Related errors


AI-assisted analysis of projectdiscovery/nuclei@265b3a3dec (2026-08-15). Data as JSON: /api/errors/b9b4c35e5171766d. Report an issue: GitHub.